golang底层 关键字
go newproc new newobject make makeslice,makechan,makemap,makemap_small...... <- chansend1,chanrecv1,chanrecv2 close closechan select selectgo -------------------------------------------------------------------------------- make、new var a = make([]int, 10, 20) 会调用 makeslice var a = new([]int) 会调用 newobject func makeslice(et *_type, len, cap int) slice { maxElements := maxSliceCap(et.size) if len < 0 || uintptr(len) > maxElements { panic(errorString("makeslice: len out of range")) } if cap < len || uintptr(cap) > maxElements { panic(errorString("makeslice: cap out of range")) } p := mallocgc(et.size*uintptr(cap), et, true) // mallocgc return slice{p, len, cap} } func newobject(typ *_type) unsafe.Pointer { return mallocgc(typ.size, typ, true) // mallocgc } -------------------------------------------------------------------------------- defer 在defer出现的地方,插入了指令call runtime.deferproc,然后在函数返回之前的地方,插入指令call runtime.deferreturn g中有一张表 _defer 记录defer的函数,调用runtime.deferproc时会将需要defer的表达式记录在表中,而在调用runtime.deferreturn的时候,则会依次从 _defer 表中出栈并执行。 defer 无法在系统栈上被调用,因此大部分运行时其实是无法使用 defer 的。 这也侧面说明 defer 必须依附于 goroutine 来完成 defer 的成本主要来源于 _defer 对象的分配与回收、被 defer 函数的参数的拷贝。 -------------------------------------------------------------------------------- panic、recover panic 和 recover 分别是 runtime.gopanic 和 runtime.gorecover 的调用 gopanic 会先判断panic是否可以恢复,例如系统栈上的 panic ,malloc 时的 panic,禁止抢占时的 panic,g 锁在 m 上时发生的 panic 等,是无法恢复的。如果可以恢复,会创建一个 _panic 链表,然后调用defer的函数,检查用户态代码是否要恢复panic,如果有recover()函数,_panic的p.recovered会被标记为true,同时 recover() 这个函数还会返回 panic 的保存相关信息 p.arg。 恢复的原则取决于 gorecover 这个方法调用方报告的 argp 是否与 p.argp 相同,仅当相同才可恢复。 由于defer的函数是记录在g中的,所以panic只会触发当前goroutine上defer的函数。 -------------------------------------------------------------------------------- select select会被翻译为 if selectnbsend(..)、if selectnbrecv(..) ,selectnbsend,selectnbrecv 会调用 chansend 和 chanrecv,其中第三个参数为false,表示不阻塞。 type scase struct { // case结构体 c *hchan // channel elem unsafe.Pointer // 接收或发送变量的地址 kind uint16 // 种类 pc uintptr releasetime int64 } const ( // kind种类 caseNil = iota caseRecv caseSend caseDefault )
select的随机:Scase数组按出现顺序记录了所有的case,pollorder数组保存着随机打乱后的下标,选择case时按pollorder里的下标来选择,每次select都重新生成pollorder数组
select-case流程控制是Go中的一个重要和独特的特性。 下面列出了官方标准运行时中select-case流程控制的实现步骤。
- 将所有case操作中涉及到的通道表达式和发送值表达式按照从上到下,从左到右的顺序一一估值。 在赋值语句中做为源值的数据接收操作对应的目标值在此时刻不需要被估值。
- 将所有case随机排序,避免饥饿问题。default分支总是排在最后。 所有case操作中相关的通道可能会有重复的。
- 为了防止在下一步中造成(和其它协程互相)死锁,对所有case操作中相关的通道进行排序。 排序依据并不重要,官方Go标准编译器使用通道的地址顺序进行排序。 排序结果中前N个通道不存在重复的情况。 N为所有case操作中涉及到的不重复的通道的数量。 下面,通道锁顺序是针对此排序结果中的前N个通道来说的,通道锁逆序是指此顺序的逆序。
- 按照上一步中的生成通道锁顺序获取所有相关的通道的锁。
- 按照第2步中生成的分支顺序检查相应分支:
- 如果这是一个case分支并且相应的通道操作是一个向关闭了的通道发送数据操作,则按照通道锁逆序解锁所有的通道并在当前协程中产生一个恐慌。 跳到第12步。
- 如果这是一个case分支并且相应的通道操作是非阻塞的,则按照通道锁逆序解锁所有的通道并执行相应的case分支代码块。 (此相应的通道操作可能会唤醒另一个处于阻塞状态的协程。) 跳到第12步。
- 如果这是default分支,则按照通道锁逆序解锁所有的通道并执行此default分支代码块。 跳到第12步。
(到这里,default分支肯定是不存在的,并且所有的case操作均为阻塞的。)
- 将当前协程(和对应case分支信息)推入到每个case操作中对应的通道的发送数据协程队列或接收数据协程队列中。 当前协程可能会被多次推入到同一个通道的这两个队列中,因为多个case操作中对应的通道可能为同一个。
- 使当前协程进入阻塞状态并则按照通道锁逆序解锁所有的通道。
- ...,当前协程处于阻塞状态,等待其它协程通过通道操作唤醒当前协程,...
- 当前协程被另一个协程中的一个通道操作唤醒。 此唤醒通道操作可能是一个通道关闭操作,也可能是一个数据发送/接收操作。 如果它是一个数据发送/接收操作,则(当前正被解释的select-case流程中)肯定有一个相应case操作与之配合传递数据。 在此配合过程中,当前协程将从相应case操作相关的通道的接收/发送数据协程队列中弹出。
- 按照第3步中的生成的通道锁顺序获取所有相关的通道的锁。
- 将当前协程从各个case操作中对应的通道的发送数据协程队列或接收数据协程队列中(可能以非弹出的方式)移除。
- 如果当前协程时被一个通道关闭操作所唤醒,则跳到第5步。
- 如果当前协程时被一个数据发送/接收操作所唤醒,则相应的case分支已经在第9步中知晓。 按照通道锁逆序解锁所有的通道并执行此case分支代码块。
- 完毕。
从此实现中,我们得知
- 一个协程可能同时多次处于同一个通道的发送数据协程队列或接收数据协程队列中。
- 当一个协程被阻塞在一个select-case流程控制中并在以后被唤醒时,它可能会从多个通道的发送数据协程队列和接收数据协程队列中被移除。