上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
6.1 延迟
延迟调用被编译器转换为 deferproc 和 deferreturn 调用。不过为提升其执行性能,编译器一直在优化,比如在栈上分配,或直接内联调用。
1.13: deferprocStack.
1.14: calling the deferred function directly. (inline)
新建
延迟调用函数被打包成 _defer,放入 G._defer 链表内。
// runtime2.go type g struct { _defer *_defer } type _defer struct { started bool heap bool sp uintptr // sp at time of defer pc uintptr // pc at time of defer fn func() // can be nil for open-coded defers link *_defer // next defer on G; can point to either heap or stack! }
// panic.go // Create a new deferred function fn, which has no arguments and results. // The compiler turns a defer statement into a call to this. func deferproc(fn func()) { gp := getg() d := newdefer() // 放到 G._defer 链表内。 d.link = gp._defer gp._defer = d d.fn = fn d.pc = getcallerpc() d.sp = getcallersp() // deferproc returns 0 normally. // a deferred func that stops a panic // makes the deferproc return 1. // the code the compiler generates always // checks the return value and jumps to the // end of the function if deferproc returns != 0. return0() // No code can go here - the C return register has // been set and must not be clobbered. }
注意,用户注册的延迟函数签名各异,编译器会为其创建统一签名的包装函数(
func()
)。将包装函数提交给 deferproc,实现间接调用。
注册延迟调用的函数,在自己栈帧内存储
{ fn, arg1, ... }
。调用(deferreturn 或 inline)包装函数
fn
前,编译器插入指令将该数据地址存入DX
寄存器。随后,包装函数通过
offset(DX)
载入参数,并以此调用实际延迟函数。
同样提供二级缓存复用方式。
垃圾回收 gcStart 会调用 clearpools 清理全局缓存。
因不再保存参数, _defer 成固定大小,取消以前版本的等级方式。
// runtime2.go type p struct { deferpool []*_defer // pool of available defer } type schedt struct { deferlock mutex deferpool *_defer }
// panic.go // Allocate a Defer, usually using per-P pool. // Each defer must be released with freedefer. The defer is not // added to any defer chain yet. func newdefer() *_defer { var d *_defer mp := acquirem() pp := mp.p.ptr() // 如果本地缓存为空,则从全局转移一批过来。 if len(pp.deferpool) == 0 && sched.deferpool != nil { lock(&sched.deferlock) for len(pp.deferpool) < cap(pp.deferpool)/2 && sched.deferpool != nil { d := sched.deferpool sched.deferpool = d.link d.link = nil pp.deferpool = append(pp.deferpool, d) } unlock(&sched.deferlock) } // 从本地缓存尾部提取一个。 if n := len(pp.deferpool); n > 0 { d = pp.deferpool[n-1] pp.deferpool[n-1] = nil pp.deferpool = pp.deferpool[:n-1] } releasem(mp) mp, pp = nil, nil // 没有缓存,则新建一个。 if d == nil { d = new(_defer) } // 堆分配标记。(对应的是在 stack 创建的 _defer) d.heap = true return d }
相比之下,栈分配的 _defer 对象更简单。在栈上预留空间,将指针作为参数,进行初始化。
// panic.go // deferprocStack queues a new deferred function with a defer record on the stack. // The defer record must have its fn field initialized. func deferprocStack(d *_defer) { gp := getg() // 提前将包装函数 fn 存入预留空间 offset(_defer.fn) 位置。 d.started = false d.heap = false d.openDefer = false d.sp = getcallersp() d.pc = getcallerpc() d.framepc = 0 d.varp = 0 *(*uintptr)(unsafe.Pointer(&d._panic)) = 0 *(*uintptr)(unsafe.Pointer(&d.fd)) = 0 // 放入 G._defer 链表。 *(*uintptr)(unsafe.Pointer(&d.link)) = uintptr(unsafe.Pointer(gp._defer)) *(*uintptr)(unsafe.Pointer(&gp._defer)) = uintptr(unsafe.Pointer(d)) return0() // No code can go here - the C return register has // been set and must not be clobbered. }
执行
在函数结束前插入的 deferreturn 会遍历 G._defer 链表,执行属于当前函数的延迟调用。
相比以前版本(jmpdefer),这个实现要干净简单得多。
// panic.go // deferreturn runs deferred functions for the caller's frame. // The compiler inserts a call to this at the end of any // function which calls defer. func deferreturn() { gp := getg() // 遍历 G._defer 链表。 for { d := gp._defer if d == nil { return } // 通过比对 _defer.sp 和 callersp 来确认属于同一函数。 // 调用堆栈所有 _defer 都在链表上,不能执行属于其他函数的延迟调用。 sp := getcallersp() if d.sp != sp { return } // 获取包装函数。 fn := d.fn // 释放 _defer 对象。 d.fn = nil gp._defer = d.link freedefer(d) // 执行包装函数。 fn() } }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论