返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

6.1 延迟

发布于 2024-10-12 19:16:00 字数 4492 浏览 0 评论 0 收藏 0

延迟调用被编译器转换为 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文