Skip to content
返回

Go Context 控制并发的核心机制

发现错误了吗?

这是一个新的分类:Golang 牛逼之路

Go 的 context 包是并发编程的核心工具,他的设计通过统一接口简化了 goroutine 的生命周期管理。其实核心就在于 context.Context 接口的四个方法:Deadline()Done()Err()Value(key)。但是呢 cancelCtxtimerCtx 是其实现的关键结构,分别用于处理主动取消和超时取消。

取消流程

cancelCtx 是支持取消操作的核心结构,源码:

type cancelCtx struct {
    Context
    mu       sync.Mutex
    done     atomic.Value
    children map[canceler]struct{}
    err      error
}

done channel

当调用 cancel() 方法的时候,他会首先会标记错误状态(比如说 CanceledDeadlineExceeded),并通过 closedchan 去关闭 done channel。具体步骤如下:

取消子节点

cancelCtx 还维护了一个子节点集合(children map[canceler]struct{}),当调用 cancel() 时会遍历所有子节点并递归调用它们的 cancel() 方法。简单来说只要执行了这个,那么就能确定取消信号能在父节点之间传播下去了:

for child := range c.children {
    child.cancel(false, err, false)
}

从父节点删除自己

假设当前 cancelCtx 的父节点也是可取消的contetxt(如 cancelCtxtimerCtx),那么就去调用 removeChild 从父节点的子节点集合中把自己删掉,释放资源:

if p, ok := c.Context.(*cancelCtx); ok {
    p.mu.Lock()
    if s, ok := p.children[c]; ok {
        delete(p.children, c)
    }
    p.mu.Unlock()
}

懒加载

done channel 用的是懒加载策略(通过 atomic.Value 存储),只会在首次调用 Done() 的时候去初始化。通过这个方式可以优化内存占用,但是还是需要在并发访问时确保原子性 。

timerCtx 的超时处理

timerCtx 直接继承了 cancelCtx,通过增加一个定时器字段就可以实现超时取消:

type timerCtx struct {
    cancelCtx
    timer *time.Timer
    deadline time.Time
}

WithDeadline

当调用 context.WithDeadline(parent Context, deadline time.Time) 的时候,发生了:

t.timer = time.AfterFunc(d, func() {
    t.cancel(true, DeadlineExceeded, false)
})

提前取消与资源释放

如果说定时器触发前就调用 cancel() 的话,就得手动停止定时器以避免资源泄漏:

func (c *timerCtx) cancel(removeFromParent bool, err error, manual bool) {
    c.cancelCtx.cancel(removeFromParent, err, manual)
    if c.timer.Stop() {
        // 定时器未触发,释放资源
    }
}

这个里面还涉及了竞态处理:

总结

context 的设计通过接口抽象还有组合模式实现了简洁高效的并发控制。cancelCtx 的递归取消和 timerCtx 的定时器机制共同实现了 Go 并发模型的核心能力。

当然了你如果不想用的话也是可以手动传递 channel 实现取消通知之类的方法啦。


发现错误了吗?

上一篇文章
Go 的 Worker Pool
下一篇文章
Go的连续栈设计