元数据
Go语言精进之路:从新手到高手的编程思想、方法和技巧1
- 书名: Go语言精进之路:从新手到高手的编程思想、方法和技巧1
- 作者: 白明
- 简介: Go入门容易,精进难,如何才能像Go开发团队那样写出符合Go思维和语言惯例的高质量代码呢?本书将从编程思维和实践技巧2个维度给出答案,帮助你在Go进阶的路上事半功倍。编程思维层面:只有真正领悟了一门语言的设计哲学和编程思维,并能将之用于实践,才算精通了这门语言。本书从Go语言设计者的视角对Go背后的设计哲学和编程思想进行了梳理和分析,指引读者体会那些看似随意实则经过深思熟虑的设计背后的秘密。实践技巧层面:实践技巧源于对Go开发团队和Go社区开发的高质量代码的阅读、挖掘和归纳,从项目结构、代码风格、语法及其实现、接口、并发、同步、错误与异常处理、测试与调试、性能优化、标准库、第三方库、工具链、最佳实践、工程实践等多个方面给出了改善Go代码质量、写出符合Go思维和惯例的代码的有效实践。学完这本书,你将拥有和Go专家一样的编程思维,写出符合Go惯例和风格的高质量代码,从众多Go初学者中脱颖而出,快速实现从Go新手到专家的转变!
- 出版时间 2022-01-01 00:00:00
- ISBN: 9787111698210
- 分类: 计算机-编程设计
- 出版社: 机械工业出版社
高亮划线
3.3 原生并发,轻量高效
- 📌 并发是有关结构的,它是一种将一个程序分解成多个小片段并且每个小片段都可以独立执行的程序设计方法;并发程序的小片段之间一般存在通信联系并且通过通信相互协作。• 并行是有关执行的,它表示同时进行一些计算任务。以上观点的重点是,并发是一种程序结构设计的方法,它使并行成为可能。 ^42557145-19-4072-4261
- ⏱ 2022-07-09 05:10:42
第10条 使用iota实现枚举常量
- 📌 Go的const语法提供了“隐式重复前一个非空表达式”的机制 ^42557145-45-1329-1359
- ⏱ 2022-07-09 06:39:21
14.3 map的内部实现
-
📌 图14-1 运行时的map类型实现 ^42557145-60-1638-1655
- ⏱ 2022-07-09 10:47:31
-
📌 nevacuate:在map扩容阶段充当扩容进度计数器。所有下标号小于nevacuate的bucket都已经完成了数据排空和迁移操作。 ^42557145-60-2084-2151
- ⏱ 2022-07-09 10:55:05
-
📌 hash0:哈希函数的种子值。 ^42557145-60-1960-1975
- ⏱ 2022-07-09 10:55:09
-
📌 extra:可选字段。如果有overflow bucket存在,且key、value都因不包含指针而被内联(inline)的情况下,该字段将存储所有指向overflow bucket的指针,保证overflow bucket是始终可用的(不被垃圾回收掉)。 ^42557145-60-2168-2296
- ⏱ 2022-07-09 11:03:23
-
📌 运行时将hashcode“一分为二”地看待,其中低位区的值用于选定bucket,高位区的值用于在某个bucket中确定key的位置。 ^42557145-60-2886-2952
- ⏱ 2022-07-09 11:26:13
-
📌 图14-2 hashcode的作用 ^42557145-60-3270-3287
- ⏱ 2022-07-09 11:26:24
-
📌 每个bucket的tophash区域是用于快速定位key位置的,这样避免了逐个key进行比较这种代价较大的操作,尤其是当key是size较大的字符串类型时,这是一种以空间换时间的思路。 ^42557145-60-3329-3421
- ⏱ 2022-07-09 11:28:42
-
📌 如果key或value的数据长度大于一定数值,那么运行时不会在bucket中直接存储数据,而是会存储key或value数据的指针。 ^42557145-60-5042-5107
- ⏱ 2022-07-10 00:49:12
第16条 理解Go语言的包导入
- 📌 已编译的Go包对应的目标文件(file_name.o或package_name.a)中不仅记录了该包本身的导出符号信息,还记录了其所依赖包的导出符号信息。 ^42557145-67-833-910
- ⏱ 2022-07-11 09:36:44
16.3 包名冲突问题
-
📌 Go编译器在编译过程中必然要使用的是编译单元(一个包)所依赖的包的源码 ^42557145-70-1772-1807
- ⏱ 2022-07-12 10:44:29
-
📌 Go源码文件头部的包导入语句中import后面的部分是一个路径,路径的最后一个分段是目录名,而不是包名 ^42557145-70-1825-1876
- ⏱ 2022-07-12 10:45:12
17.1 包级别变量声明语句中的表达式求值顺序
-
📌 在Go包中,包级别变量的初始化按照变量声明的先后顺序进行。 ^42557145-72-546-575
- ⏱ 2022-07-12 11:12:12
-
📌 包级别变量的初始化是逐步进行的,每一步就是按照变量声明顺序找到下一个“ready for initialization”变量并对其进行初始化的过程。反复重复这一步骤,直到没有“ready for initialization”变量为止。 ^42557145-72-792-910
- ⏱ 2022-07-12 11:13:10
-
📌 位于同一包内但不同文件中的变量的声明顺序依赖编译器处理文件的顺序:先处理的文件中的变量的声明顺序先于后处理的文件中的所有变量。 ^42557145-72-927-990
- ⏱ 2022-07-12 11:13:41
17.2 普通求值顺序
-
📌 y[f()], _ = g(h(), i()+x[j()], <-c), k()这行语句是赋值语句,但赋值语句的表达式操作数中包含函数调用、channel操作。按照普通求值规则,这些函数调用、channel操作按从左到右的顺序进行求值。 ^42557145-73-1315-1433
- ⏱ 2022-07-12 11:04:25
-
📌 当普通求值顺序与包级变量的初始化依赖顺序一并使用时,后者优先级更高,但每个单独表达式中的操作数求值依旧按照普通求值顺序的规则。 ^42557145-73-2040-2103
- ⏱ 2022-07-12 11:14:55
17.3 赋值语句的求值
- 📌 赋值语句求值分为两个阶段:1)第一阶段,对于等号左边的下标表达式、指针解引用表达式和等号右边表达式中的操作数,按照普通求值规则从左到右进行求值;2)第二阶段,按从左到右的顺序对变量进行赋值。 ^42557145-74-545-698
- ⏱ 2022-07-12 11:20:50
17.4 switch/select语句中的表达式求值
-
📌 接下来将按照从上到下、从左到右的顺序对case语句中的表达式进行求值。如果某个表达式的结果与switch表达式结果一致,那么求值停止,后面未求值的case表达式将被忽略。 ^42557145-75-1301-1386
- ⏱ 2022-07-12 11:24:18
-
📌 fallthrough将执行权直接转移到下一个case执行语句中了,略过了case表达式Expr(4)的求值。 ^42557145-75-1502-1557
- ⏱ 2022-07-12 11:28:04
-
📌 func main() { switch Expr(2) { case Expr(1), Expr(2), Expr(3): fmt.Println(“enter into case1”) fallthrough case Expr(4): fmt.Println(“enter into case2”) } } ^42557145-75-789-1012
- ⏱ 2022-07-12 11:28:30
-
📌 对于switch-case语句而言,首先进行求值的是switch后面的表达式Expr(2),这个表达式在求值时输出2。 ^42557145-75-1211-1299
- ⏱ 2022-07-12 11:29:19
-
📌 select执行开始时,首先所有case表达式都会被按出现的先后顺序求值一遍。 ^42557145-75-2935-2974
- ⏱ 2022-07-12 11:36:07
-
📌 有一个例外,位于case等号左边的从channel接收数据的表达式(RecvStmt)不会被求值 ^42557145-75-3098-3146
- ⏱ 2022-07-12 11:37:35
18.1 Go代码块与作用域简介
-
📌 每个if、for和switch语句均被视为位于其自己的隐式代码块中。 ^42557145-77-1343-1377
- ⏱ 2022-07-12 11:50:53
-
📌 switch或select语句中的每个子句都被视为一个隐式代码块。 ^42557145-77-1394-1427
- ⏱ 2022-07-12 11:51:00
18.3 其他控制语句的代码块规则简介
- 📌 case RecvStmt: { // 隐式代码块2开始,如果RecvStmt声明了新变量,那么该变量也应包含在隐式代码块2中 ^42557145-79-4258-4328
- ⏱ 2022-07-12 12:06:18
第19条 了解Go语言控制语句惯用法及使用注意事项
- 📌 switch的case语句支持表达式列表 ^42557145-80-745-765
- ⏱ 2022-07-12 12:08:09
19.2 for range的避“坑”指南
-
📌 参与迭代的是range表达式的副本 ^42557145-82-2401-2418
- ⏱ 2022-07-12 13:16:46
-
📌 例子中for-range循环的等价伪代码如下:for i, v := range a’ { //a’是a的一个值副本 ^42557145-82-3372-3445
- ⏱ 2022-07-12 13:23:00
-
📌 虽然长度是Go数组类型的一部分,但长度并不包含在数组类型在Go运行时的内部表示中,数组长度是由编译器在编译期计算出来。 ^42557145-82-3573-3632
- ⏱ 2022-07-12 13:25:15
-
📌 如果作为range表达式的字符串s中存在非法UTF8字节序列,那么v将返回0xfffd这个特殊值,并且在下一轮循环中,v将仅前进一字节 ^42557145-82-7663-7730
- ⏱ 2022-07-12 13:42:54
-
📌 for range无法保证每次迭代的元素次序是一致的。同时,如果在循环的过程中对map进行修改,那么这样修改的结果是否会影响后续迭代过程也是不确定的 ^42557145-82-8757-8838
- ⏱ 2022-07-12 14:05:45
19.3 break跳到哪里去了
- 📌 明确规定break语句(不接label的情况下)结束执行并跳出的是同一函数内break语句所在的最内层的for、switch或select的执行。 ^42557145-83-1443-1516
- ⏱ 2022-07-12 14:19:04
20.1 认识init函数
- 📌 一个包内的、分布在多个文件中的多个init函数的执行次序是什么样的呢?一般来说,先被传递给Go编译器的源文件中的init函数先被执行,同一个源文件中的多个init函数按声明顺序依次执行。但Go语言的惯例告诉我们:不要依赖init函数的执行次序。 ^42557145-87-1158-1309
- ⏱ 2022-07-12 14:43:56
20.2 程序初始化顺序
-
📌 按照常量→变量→init函数的顺序进行初始化; ^42557145-88-1249-1272
- ⏱ 2022-07-12 14:46:20
-
📌 init函数适合做包级数据的初始化及初始状态检查工作的前提条件是,init函数的执行顺位排在其所在包的包级变量之后。 ^42557145-88-1712-1806
- ⏱ 2022-07-12 14:47:36
20.3 使用init函数检查包级变量的初始状态
-
📌 func init() { sql.Register(“postgres”, &Driver{})} ^42557145-89-3684-3743
- ⏱ 2022-07-12 15:13:05
-
📌 // github.com/lib/pq/conn.go ^42557145-89-3647-3677
- ⏱ 2022-07-12 15:13:11
-
📌 空别名方式导入lib/pq的副作用就是Go运行时会将lib/pq作为main包的依赖包并会初始化pq包,于是pq包的init函数得以执行。我们看到在pq包的init函数中,pq包将自己实现的SQL驱动(driver)注册到sql包中。这样,只要应用层代码在打开数据库的时候传入驱动的名字(这里是postgres),通过sql.Open函数返回的数据库实例句柄对应的就是pq这个驱动的相应实现。 ^42557145-89-3781-3977
- ⏱ 2022-07-12 15:14:04
-
📌 一旦init函数在检查包数据初始状态时遇到失败或错误的情况(尽管极少出现) ^42557145-89-5562-5599
- ⏱ 2022-07-12 15:17:43
-
📌 我们一般建议直接调用panic或者通过log.Fatal等函数记录异常日志,然后让程序快速退出。 ^42557145-89-5658-5706
- ⏱ 2022-07-12 15:17:50
21.2 函数作为“一等公民”的特殊运用
-
📌 函子(functor ^42557145-92-5447-5457
- ⏱ 2022-07-12 18:34:05
-
📌 函数式编程有一种被称为延续传递式(Continuation-passing Style,CPS)的编程风格可以充分运用函数作为“一等公民”的特质。 ^42557145-92-8473-8546
- ⏱ 2022-07-12 18:40:59
-
📌 在CPS风格中,函数是不允许有返回值的。一个函数A应该将其想返回的值显式传给一个continuation函数(一般接受一个参数) ^42557145-92-8575-8639
- ⏱ 2022-07-14 08:50:27
-
📌 将返回结果传给continuation函数,即把return语句替换为对f函数的调用 ^42557145-92-9196-9238
- ⏱ 2022-07-14 08:52:33
-
📌 将factorial实现中的返回结果传给continuation函数,即把return语句替换为对f函数的调用 ^42557145-92-9894-9949
- ⏱ 2022-07-14 09:07:02
22.2 defer的常见用法
-
📌 拦截panic ^42557145-95-510-517
- ⏱ 2022-07-14 10:25:29
-
📌 无法拦截并恢复一些运行时之外的致命问题 ^42557145-95-2007-2026
- ⏱ 2022-07-14 10:36:01
22.3 关于defer的几个关键问题
- 📌 defer关键字后面的表达式是在将deferred函数注册到deferred函数栈的时候进行求值的。 ^42557145-96-2643-2693
- ⏱ 2022-07-14 13:26:43
第23条 理解方法的本质以选择正确的receiver类型
-
📌 方法定义要与类型定义放在同一个包内。由此我们可以推出:不能为原生类型(如int、float64、map等)添加方法,只能为自定义类型定义方法 ^42557145-97-1023-1129
- ⏱ 2022-07-14 13:41:25
-
📌 receiver参数的基类型本身不能是指针类型或接口类型 ^42557145-97-1633-1661
- ⏱ 2022-07-14 13:43:21
23.1 方法的本质
- 📌 这种直接以类型名T调用方法的表达方式被称为方法表达式(Method Expression)。 ^42557145-98-1256-1338
- ⏱ 2022-07-14 13:48:54
24.1 方法集合
- 📌 类型T的方法集合则包含所有receiver为T和T类型的方法 ^42557145-102-2298-2330
- ⏱ 2022-07-14 14:26:58
24.2 类型嵌入与方法集合
-
📌 结构体类型在嵌入某接口类型的同时,也实现了这个接口。这一特性在单元测试中尤为有用 ^42557145-103-6094-6141
- ⏱ 2022-07-14 14:50:52
-
📌 type T struct { T1 *T2} ^42557145-103-8252-8289
- ⏱ 2022-07-14 16:11:42
-
📌 T类型的方法集合 = T1的方法集合 + *T2的方法集合;*T类型的方法集合 = *T1的方法集合 + *T2的方法集合。 ^42557145-103-9625-9704
- ⏱ 2022-07-14 16:12:08
24.3 defined类型的方法集合
-
📌 基于接口类型创建的defined类型与原接口类型的方法集合是一致的 ^42557145-104-1625-1658
- ⏱ 2022-07-14 16:21:59
-
📌 而基于自定义非接口类型创建的defined类型则并没有“继承”原类型的方法集合,新的defined类型的方法集合是空的。 ^42557145-104-1701-1797
- ⏱ 2022-07-14 16:22:05
24.4 类型别名的方法集合
-
📌 类型别名与原类型拥有完全相同的方法集合,无论原类型是接口类型还是非接口类型。 ^42557145-105-1636-1674
- ⏱ 2022-07-14 16:24:34
-
📌 类型T的方法集合是以T为receiver类型的所有方法的集合,类型T的方法集合是以T为receiver类型的所有方法的集合与类型T的方法集合的并集; ^42557145-105-2004-2080
- ⏱ 2022-07-14 16:26:11
25.4 实现功能选项模式
- 📌 (functional option) ^42557145-110-5561-5580
- ⏱ 2022-07-14 18:02:28
26.2 接口类型变量的内部表示
-
📌 eface:用于表示没有方法的空接口(empty interface)类型变量,即interface{}类型的变量。iface:用于表示其余拥有方法的接口(interface)类型变量。 ^42557145-114-807-917
- ⏱ 2022-07-14 18:26:09
-
📌 我们要判断两个接口类型变量是否相同,只需判断_type/tab是否相同以及data指针所指向的内存空间所存储的数据值是否相同(注意:不是data指针的值)。 ^42557145-114-3921-3999
- ⏱ 2022-07-15 00:01:13
-
📌 Go运行时会为程序内的全部类型建立只读的共享_type信息表, ^42557145-114-3749-3780
- ⏱ 2022-07-15 00:01:59
-
📌 err1 = (*T)(nil)针对这种赋值,println输出的err1是(0x10ed120, 0x0),即非空接口类型变量的类型信息并不为空,数据指针为空,因此它与nil(0x0,0x0)之间不能画等号。 ^42557145-114-7579-7715
- ⏱ 2022-07-15 00:16:22
29.3 以接口为连接点的水平组合
-
📌 包裹函数(wrapper function)的形式是这样的:它接受接口类型参数,并返回与其参数类型相同的返回值。 ^42557145-125-1607-1663
- ⏱ 2022-07-15 05:16:04
-
📌 适配器函数类型(adapter function type)是一个辅助水平组合实现的“工具”类型。 ^42557145-125-3907-3956
- ⏱ 2022-07-15 05:28:53
32.2 goroutine调度模型与演进过程
-
📌 P是一个“逻辑处理器”,每个G要想真正运行起来,首先需要被分配一个P,即进入P的本地运行队列(local runq)中 ^42557145-135-1953-2012
- ⏱ 2022-07-16 09:03:13
-
📌 对于G来说,P就是运行它的“CPU”,可以说在G的眼里只有P。但从goroutine调度器的视角来看,真正的“CPU”是M,只有将P和M绑定才能让P的本地运行队列中的G真正运行起来。 ^42557145-135-2042-2169
- ⏱ 2022-07-16 09:06:10
-
📌 对于常规文件的I/O操作一旦阻塞,那么线程(M)将进入挂起状态,等待I/O返回后被唤醒。这种情况下P将与挂起的M分离,再选择一个处于空闲状态(idle)的M。如果此时没有空闲的M,则会新创建一个M(线程),这就是大量文件I/O操作会导致大量线程被创建的原因。 ^42557145-135-3281-3410
- ⏱ 2022-07-16 09:17:52
-
📌 Go运行时已经实现了netpoller,这使得即便G发起网络I/O操作也不会导致M被阻塞(仅阻塞G),因而不会导致大量线程(M)被创建出来。 ^42557145-135-3209-3279
- ⏱ 2022-07-16 09:18:06
32.3 对goroutine调度器原理的进一步理解
-
📌 P:代表逻辑processor,P的数量决定了系统内最大可并行的G的数量 ^42557145-136-742-778
- ⏱ 2022-07-23 23:00:48
-
📌 调度循环的机制大致是从各种队列、P的本地运行队列中获取G,切换到G的执行栈上并执行G的函数,调用goexit做清理工作并回到M。如此反复。 ^42557145-136-885-954
- ⏱ 2022-07-23 23:04:23
-
📌 收回因syscall长时间阻塞的P ^42557145-136-2896-2913
- ⏱ 2022-07-24 23:02:51
-
📌 在Go程序启动时,运行时会启动一个名为sysmon的M(一般称为监控线程),该M的特殊之处在于它无须绑定P即可运行(以g0这个G的形式)。 ^42557145-136-2034-2103
- ⏱ 2022-07-24 23:04:34
-
📌 如果一个G任务运行超过10ms,sysmon就会认为其运行时间太久而发出抢占式调度的请求。 ^42557145-136-3480-3525
- ⏱ 2022-07-24 23:06:41
-
📌 如果G被阻塞在某个channel操作或网络I/O操作上,那么G会被放置到某个等待队列中,而M会尝试运行P的下一个可运行的G。 ^42557145-136-3714-3776
- ⏱ 2022-07-24 23:10:02
-
📌 如果G被阻塞在某个系统调用上,那么不仅G会阻塞,执行该G的M也会解绑P(实质是被sysmon抢走了),与G一起进入阻塞状态。如果此时有空闲的M,则P会与其绑定并继续执行其他G;如果没有空闲的M,但仍然有其他G要执行,那么就会创建一个新M(线程)。当系统调用返回后,阻塞在该系统调用上的G会尝试获取一个可用的P,如果有可用P,之前运行该G的M将绑定P继续运行G;如果没有可用的P,那么G与M之间的关联将解除,同时G会被标记为runnable,放入全局的运行队列中,等待调度器的再次调度。 ^42557145-136-3962-4204
- ⏱ 2022-07-24 23:16:10
32.4 调度器状态的查看方法
-
📌 使用Go运行时环境变量GODEBUG ^42557145-137-424-442
- ⏱ 2022-07-24 23:18:28
-
📌 “schedtrace=1000”,其含义就是每1000ms打印输出一次goroutine调度器的状态 ^42557145-137-1372-1423
- ⏱ 2022-07-24 23:18:44
33.1 Go并发模型
-
📌 Tony Hoare提出的CSP(Communicating Sequential Process,通信顺序进程)模型,如 ^42557145-140-1360-1457
- ⏱ 2022-07-24 23:56:18
-
📌 Tony Hoare认为输入/输出应该是基本的编程原语,数据处理逻辑(CSP中的P)仅需调用输入原语获取数据,顺序处理数据,并将结果数据通过输出原语输出即可。因此,在Tony Hoare眼中,一个符合CSP模型的并发程序应该是一组通过输入/输出原语连接起来的P的集合。 ^42557145-140-1859-1993
- ⏱ 2022-07-25 00:02:17
33.2 Go常见的并发模式
-
📌 在语言层面,Go针对CSP模型提供了三种并发原语。goroutine:对应CSP模型中的P,封装了数据的处理逻辑,是Go运行时调度的基本执行单元。channel:对应CSP模型中的输入/输出原语,用于goroutine之间的通信和同步。select:用于应对多路输入/输出,可以让goroutine同时协调处理多个channel操作。 ^42557145-141-405-810
- ⏱ 2022-07-25 00:05:37
-
📌 Go中最常见的goroutine创建模式 ^42557145-141-1764-1784
- ⏱ 2022-07-25 09:22:22
-
📌 每个P都有一个运行在后台的用于标记的G ^42557145-141-3508-3529
- ⏱ 2022-07-25 09:33:54
-
📌 close(job) // 广播给所有新goroutine wg.Wait() ^42557145-141-11772-11822
- ⏱ 2022-07-25 10:08:32
-
📌 “超时等待退出”框架 ^42557145-141-13256-13266
- ⏱ 2022-07-25 10:13:44
-
📌 图33-3 管道模式 ^42557145-141-18421-18431
- ⏱ 2022-07-25 11:36:15
-
📌 图33-4 扇出模式和扇入模式 ^42557145-141-21559-21574
- ⏱ 2022-07-25 11:48:39
-
📌 利用context包实现取消模式 ^42557145-141-27949-27965
- ⏱ 2022-07-25 11:58:58
-
📌 req = req.WithContext(ctx) ^42557145-141-28530-28558
- ⏱ 2022-07-25 12:02:01
-
📌 req, err := http.NewRequest(“GET”, url, nil) ^42557145-141-28337-28383
- ⏱ 2022-07-25 12:02:19
-
📌 resp, err := http.DefaultClient.Do(req) ^42557145-141-28640-28681
- ⏱ 2022-07-25 12:03:12
-
📌 c := make(chan result) ctx, cancel := context.WithCancel(context.Background()) defer cancel() ^42557145-141-28140-28245
- ⏱ 2022-07-25 12:06:36
-
📌 c := make(chan result, len(servers)) ^42557145-141-26591-26629
- ⏱ 2022-07-25 12:06:49
-
📌 resp, err := http.Get(url) ^42557145-141-26714-26742
- ⏱ 2022-07-25 12:06:55
34.1 无缓冲channel
-
📌 发送动作一定发生在接收动作完成之前;接收动作一定发生在发送动作完成之前。 ^42557145-143-1516-1569
- ⏱ 2022-07-25 13:10:52
-
📌 close(groupSignal) ^42557145-143-4355-4373
- ⏱ 2022-07-25 13:21:17
-
📌 用作信号传递 ^42557145-143-2177-2183
- ⏱ 2022-07-25 13:26:54
-
📌 用于替代锁机制 ^42557145-143-7434-7441
- ⏱ 2022-07-25 13:27:09
34.2 带缓冲channel
-
📌 用作消息队列 ^42557145-144-892-898
- ⏱ 2022-07-25 13:42:50
-
📌 用作计数信号量 ^42557145-144-3504-3511
- ⏱ 2022-07-25 13:47:53
-
📌 一个发往带缓冲channel的发送操作表示获取一个信号量槽位,而一个来自带缓冲channel的接收操作则表示释放一个信号量槽位。 ^42557145-144-3696-3760
- ⏱ 2022-07-25 14:02:08
-
📌 但有且只有一个接收者。在这样的场景下,我们可以在接收者goroutine中 ^42557145-144-9580-9653
- ⏱ 2022-07-25 14:21:59
-
📌 但有且只有一个发送者。在这样的场景下,我们可以在发送goroutine中 ^42557145-144-9744-9816
- ⏱ 2022-07-25 14:22:07
34.3 nil channel的妙用
- 📌 没有初始化的channel(nil channel)进行读写操作将会发生阻塞 ^42557145-145-411-449
- ⏱ 2022-07-25 14:22:34
35.1 sync包还是channel
- 📌 基于channel的并发设计的一个特点是,在goroutine间通过channel转移数据对象的所有权。只有拥有数据对象所有权(从channel接收到该数据)的goroutine才可以对该数据对象进行状态变更。 ^42557145-148-1843-1948
- ⏱ 2022-07-25 14:54:37
35.2 使用sync包的注意事项
-
📌 state:表示当前互斥锁的状态。sema:用于控制锁状态的信号量。 ^42557145-149-2860-2911
- ⏱ 2022-07-25 15:03:36
-
📌 在使用sync包中类型时,推荐通过闭包方式或传递类型实例(或包裹该类型的类型实例)的地址或指针的方式进行 ^42557145-149-3518-3642
- ⏱ 2022-07-25 15:09:02
35.4 条件变量
-
📌 使用条件变量原语,我们可以在实现相同目标的同时避免对条件的轮询。 ^42557145-151-2164-2196
- ⏱ 2022-07-25 15:38:17
-
📌 groupSignal.L.Lock() for !ready { groupSignal.Wait() } groupSignal.L.Unlock() ^42557145-151-2741-2876
- ⏱ 2022-07-25 15:42:02
-
📌 groupSignal.L.Lock() ready = true groupSignal.Broadcast() groupSignal.L.Unlock() ^42557145-151-3385-3482
- ⏱ 2022-07-25 15:43:01
-
📌 Wait方法在goroutine挂起前会进行Unlock操作。 ^42557145-151-4306-4337
- ⏱ 2022-07-25 15:44:13
-
📌 在Wait方法返回前,Wait方法会再次加锁让goroutine进入临界区。 ^42557145-151-4449-4487
- ⏱ 2022-07-25 15:44:39
35.6 使用sync.Pool减轻垃圾回收压力
-
📌 sync.Pool为每个P(goroutine调度模型中的P)单独建立一个local缓存池,进一步降低高并发下对锁的争抢。 ^42557145-153-713-774
- ⏱ 2022-07-25 15:55:38
-
📌 限制要放回缓存池中的数据对象大小 ^42557145-153-2544-2560
- ⏱ 2022-07-25 17:54:33
-
📌 建立多级缓存池 ^42557145-153-3187-3194
- ⏱ 2022-07-25 17:54:38
-
📌 sync包内定义的结构体或包含这些类型的结构体在首次使用后禁止复制 ^42557145-153-4814-4847
- ⏱ 2022-07-25 17:59:04
36.1 atomic包与原子操作
- 📌 原子操作由底层硬件直接提供支持,是一种硬件实现的指令级“事务” ^42557145-155-840-871
- ⏱ 2022-07-25 18:03:07
36.3 对共享自定义类型变量的无锁读写
- 📌 atomic通过Value类型的装拆箱操作实现了对任意自定义类型的原子操作(Load和Store) ^42557145-157-441-490
- ⏱ 2022-07-25 18:15:09
37.1 构造错误值
-
📌 错误是值,只是以error接口变量的形式统一呈现 ^42557145-160-489-513
- ⏱ 2022-07-25 18:22:14
-
📌 errors.errorString ^42557145-160-1311-1329
- ⏱ 2022-07-25 18:29:00
-
📌 当我们在格式化字符串中使用%w时,fmt.Errorf返回的错误值的底层类型为fmt.wrapError ^42557145-160-1524-1576
- ⏱ 2022-07-25 18:29:10
-
📌 在标准库中,Go提供了构造错误值的两种基本方法——errors.New和fmt.Errorf ^42557145-160-998-1044
- ⏱ 2022-07-25 18:39:18
37.2 透明错误处理策略
-
📌 错误处理方并不关心错误值的上下文 ^42557145-161-830-846
- ⏱ 2022-07-25 18:40:43
-
📌 透明错误处理策略 ^42557145-161-366-374
- ⏱ 2022-07-25 19:12:18
37.3 “哨兵”错误处理策略
-
📌 定义导出的(exported)“哨兵”错误值 ^42557145-162-1074-1096
- ⏱ 2022-07-25 18:44:52
-
📌 Is方法类似于将一个error类型变量与“哨兵”错误值的比较 ^42557145-162-2148-2178
- ⏱ 2022-07-25 18:56:14
-
📌 不同的是,如果error类型变量的底层错误值是一个包装错误(wrap error),errors.Is方法会沿着该包装错误所在错误链(error chain)与链上所有被包装的错误(wrapped error)进行比较,直至找到一个匹配的错误。 ^42557145-162-2314-2436
- ⏱ 2022-07-25 18:56:22
37.4 错误值类型检视策略
-
📌 使用Go提供的类型断言机制(type assertion)或类型选择机制(type switch) ^42557145-163-632-753
- ⏱ 2022-07-25 18:52:13
-
📌 As方法类似于通过类型断言判断一个error类型变量是否为特定的自定义错误类型 ^42557145-163-2304-2343
- ⏱ 2022-07-25 18:56:36
-
📌 不同的是,如果error类型变量的底层错误值是一个包装错误,那么errors.As方法会沿着该包装错误所在错误链与链上所有被包装的错误的类型进行比较,直至找到一个匹配的错误类型。 ^42557145-163-2518-2607
- ⏱ 2022-07-25 18:56:41
-
📌 错误值类型检视策略 ^42557145-163-366-375
- ⏱ 2022-07-25 19:11:36
37.5 错误行为特征检视策略
-
📌 将某个包中的错误类型归类,统一提取出一些公共的错误行为特征(behaviour),并将这些错误行为特征放入一个公开的接口类型中。 ^42557145-164-482-546
- ⏱ 2022-07-25 18:59:22
-
📌 错误行为特征检视策略 ^42557145-164-366-376
- ⏱ 2022-07-25 19:11:17
-
📌 尽量使用透明错误处理策略降低错误处理方与错误值构造方之间的耦合;如果可以通过错误值类型的特征进行错误检视,那么尽量使用错误行为特征检视策略;在上述两种策略无法实施的情况下,再用“哨兵”策略和错误值类型检视策略; ^42557145-164-2282-2421
- ⏱ 2022-07-25 19:13:32
38.3 优化思路
-
📌 圈复杂度是一种代码复杂度的衡量标准 ^42557145-168-1566-1583
- ⏱ 2022-07-25 22:30:12
-
📌 圈复杂度可以通过程序控制流图计算,公式为V(G) = e + 2 - n。其中:e为控制流图中边的数量;n为控制流图中节点的数量 ^42557145-168-1687-1751
- ⏱ 2022-07-25 22:31:14
39.1 Go的panic不是Java的checked exception
-
📌 checked exception实质是错误,而panic是异常 ^42557145-170-1078-1110
- ⏱ 2022-07-25 22:58:19
-
📌 API调用者没有义务处理panic ^42557145-170-4120-4137
- ⏱ 2022-07-25 23:03:49
39.2 panic的典型应用
- 📌 充当断言角色,提示潜在bug ^42557145-171-691-705
- ⏱ 2022-07-25 23:07:05
39.3 理解panic的输出信息
-
📌 栈跟踪输出的信息是在函数调用过程中的“快照”信息,因此一些输出数值虽然看似不合理,但由于其并不是最终值,问题也不一定发生在它们身上,比如返回值参数。 ^42557145-172-4163-4237
- ⏱ 2022-07-25 23:21:55
-
📌 很多简单的函数或方法会被自动内联(inline)。函数一旦内联化,我们就无法在栈跟踪信息中看到栈帧信息了,栈帧信息都变成了省略号 ^42557145-172-6211-6275
- ⏱ 2022-07-25 23:25:40
-
📌 要想看到栈跟踪信息中的栈帧数据,我们需要使用-gcflags=“-l”来告诉编译器不要执行内联优化 ^42557145-172-6563-6612
- ⏱ 2022-07-25 23:26:03
读书笔记
14.3 map的内部实现
划线评论
- 📌 key、value都因不包含指针而被内联(inline) ^3533118-7AClz4Gq3
- 💭 改overflow指针为uintptr,标记整个map为非指针,避免GC扫描优化性能。原overflow指针保存到extra的数组中,避免被垃圾回收。
- ⏱ 2022-07-09 11:23:51
21.2 函数作为“一等公民”的特殊运用
划线评论
- 📌 factorial(n-1, func(y int) { f(n * y) }) ^3533118-7AJOIKkqS
- 💭 这里y代表factorial(n-1)
- ⏱ 2022-07-14 09:15:43
32.3 对goroutine调度器原理的进一步理解
划线评论
- 📌 向长时间运行的G任务发出抢占调度 ^3533118-7AZUgYhM9
- 💭 10ms
- ⏱ 2022-07-24 23:07:13
33.2 Go常见的并发模式
划线评论
- 📌 ConcurrentShutdown(10*time.Second, ShutdownerFunc(f1), ^3533118-7B0FXyk6D
- 💭 这例子的shutdowner在Shutdown(waitTimout)中并没有使用waitTimeout参数,只使用了自己实例化时指定的运行时间
- ⏱ 2022-07-25 11:15:21
38.3 优化思路
划线评论
- 📌 公式为V(G) = e + 2 - n ^3533118-7B1oDCTKn
- 💭 就是计算面数的公式
- ⏱ 2022-07-25 22:37:32
