深入理解 Context
深入理解 Context
概览
Golang 的 context 包很多时候被用来处理 Goroutine 链的数据共享,退出等操作。
这里主要描述 Context 的使用示例和内部实现。
特性
-
Context 是并发安全的。
-
支持树状的上级控制下级,不支持反向控制和平级控制。
接口
type Context interface{
Deadline() (deadline time.Time, ok bool) // 返回被取消的时间
Done() <-chan struct{} // 返回一个 Channel,当任务完成或被取消时这个 Channel 会被关闭,多次调用 Done 会返回同一个 Channel
Err() error // 返回 Context 结束的原因
Value(key interface{}) interface{}
}
官方示例
package main
import (
"context"
"fmt"
)
func main() {
gen := func(ctx context.Context) <-chan int {
dst := make(chan int)
n := 1
go func() {
for {
select {
case <-ctx.Done():
return // returning not to leak the goroutine
case dst <- n:
n++
}
}
}()
return dst
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // cancel when we are finished consuming integers
for n := range gen(ctx) {
fmt.Println(n)
if n == 5 {
break
}
}
}
CancelContext 的内部实现
Context 的实现
context 本质上是一个这样的接口
type Context interface{
Deadline() (deadline time.Time, ok bool)
Done() <- chan struct{}
Err() error
Value(key interface{}) interface{}
}
分别包含了生存信号,取消信号,Goroutine 之间共享的值。
包内针对这个接口实现了 emptyCtx,返回值都是零值。
CancelCtx
创建 cancelCtx 实例主要做了一下几件事。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent) // new 一个 cancelCtx 实例
propagateCancel(parent, &c) // 在父节点上挂载 cancel 信息
return &c, func() { c.cancel(true, Canceled) } //什么意思,看下文您就明白了
}
propagateCancel 这个方法如下
func propagateCancel(parent Context, child canceler) {
// 父节点是一个空节点,可以理解为本节点为根节点,不需要挂载
if parent.Done() == nil {
return // parent is never canceled
}
// 父节点是可取消类型的
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock()
if p.err != nil {
// parent has already been canceled
// 父节点被取消了,本节点也需要取消
child.cancel(false, p.err)
} else {
if p.children == nil {
p.children = make(map[canceler]struct{})
}
// 挂载到父节点
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
// 为了兼容,Context 内嵌到一个类型里的情况发生
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
cancel 的方法实现如下
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
if err == nil {
panic("context: internal error: missing cancel error")
}
c.mu.Lock()
// 已经被取消
if c.err != nil {
c.mu.Unlock()
return
}
// 设置 cancelCtx 错误信息
c.err = err
if c.done == nil {
c.done = closedchan
} else {
close(c.done)
}
// 递归地取消所有子节点
for child := range c.children {
// NOTE: acquiring the child's lock while holding parent's lock.
child.cancel(false, err)
}
// 清空子节点
c.children = nil
c.mu.Unlock()
if removeFromParent {
// 从父节点中移除自己
removeChild(c.Context, c)
}
}
主要工作:
-
设置错误信息
-
关闭 channel
-
递归的取消子节点
-
从父节点中移除自己
参考
Read other posts