分享
  1. 首页
  2. 文章

golang源码学习之context

ihornet · · 1205 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

本文不对context的具体用法讲解,仅对源码走读。
context一共分为6类:Background、TODO、WithCancel、WithDeadline、WithTimeout、WithValue,下面依次走读。
数据结构
// context/context.go
type Context interface {
 // 返回deadline和true, ok写死为true
 Deadline() (deadline time.Time, ok bool)
 // 用于cancel监听
 Done() <-chan struct{}
 // 返回错误消息
 Err() error
 // 获取WithValue存储的value
 Value(key interface{}) interface{}
}
Background、TODO
// context/context.go
var (
 background = new(emptyCtx)
 todo = new(emptyCtx)
)
func Background() Context {
 return background
}
func TODO() Context {
 return todo
}

可以看出Background和TODO其实是一样的

// context/context.go
type emptyCtx int
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
 return
}
func (*emptyCtx) Done() <-chan struct{} {
 return nil
}
func (*emptyCtx) Err() error {
 return nil
}
func (*emptyCtx) Value(key interface{}) interface{} {
 return nil
}
func (e *emptyCtx) String() string {
 switch e {
 case background:
 return "context.Background"
 case todo:
 return "context.TODO"
 }
 return "unknown empty Context"
}

emptyCtx 的各个方法都返回的是零值。注意Done()返回nil,下面会利用它判断是否是Background和TODO。

WithCancel
// context/context.go
type cancelCtx struct {
 // 这里指的是parent
 Context
 mu sync.Mutex // protects following fields
 // 其实就是Done()返回值
 done chan struct{} // created lazily, closed by first cancel call
 // child集合
 children map[canceler]struct{} // set to nil by the first cancel call
 //错误消息
 err error // set to non-nil by the first cancel call
}
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
 // 创建cancelCtx
 c := newCancelCtx(parent)
 propagateCancel(parent, &c)
 return &c, func() { c.cancel(true, Canceled) }
}
func propagateCancel(parent Context, child canceler) {
 // 不具备cancel功能, Done()其实是返回一个叫done的成员变量, 而done是在cancel()里面赋值的
 // 这里指的是context.Background()、context.TODO()
 if parent.Done() == nil {
 return // parent is never canceled
 }
 // parent是否属于cancelCtx
 if p, ok := parentCancelCtx(parent); ok {
 p.mu.Lock()
 // parent已取消
 if p.err != nil {
 // parent has already been canceled
 child.cancel(false, p.err) // parent取消了,child直接执行取消
 } else {
 if p.children == nil {
 p.children = make(map[canceler]struct{})
 }
 // 如果parent没有取消,那么就将child加入到parent的children集合中
 p.children[child] = struct{}{}
 }
 p.mu.Unlock()
 } else { // 如果parent不属于cancelCtx
 go func() {
 select {
 case <-parent.Done():
 child.cancel(false, parent.Err()) // parent被cancel时,child执行cancel
 case <-child.Done():
 }
 }()
 }
}
// parent是否属于cancelCtx。
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
 for {
 switch c := parent.(type) {
 case *cancelCtx: // 这里指context.WithCancel
 return c, true
 case *timerCtx: // 这里指context.WithDeadline、context.WithTimeout
 return &c.cancelCtx, true
 case *valueCtx: // 这里指 context.WithValue
 parent = c.Context
 default:
 return nil, false
 }
 }
}
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
 if err == nil {
 panic("context: internal error: missing cancel error")
 }
 c.mu.Lock()
 // 多次cancel得到的err是一样的
 if c.err != nil {
 c.mu.Unlock()
 return // already canceled
 }
 c.err = err
 if c.done == nil {
 c.done = closedchan
 } else {
 close(c.done) //关闭channel
 }
 // 遍历孩子执行取消操作。
 for child := range c.children {
 // NOTE: acquiring the child's lock while holding parent's lock.
 //孩子执行取消操作, 为什么这里传false呢? 应为我们在第一次做cancel时removeFromParent=true已经脱离parant了
 child.cancel(false, err)
 }
 c.children = nil
 c.mu.Unlock()
 if removeFromParent {
 // 这里的c.Context指的是parent
 removeChild(c.Context, c)
 }
}
func removeChild(parent Context, child canceler) {
 p, ok := parentCancelCtx(parent)
 // 因为我们是在parent属于cancelCtx的时候才将child加入的,所以这里直接return
 if !ok {
 return
 }
 p.mu.Lock()
 if p.children != nil {
 delete(p.children, child) // 移除child
 }
 p.mu.Unlock()
}

注意:只有cancelCtx才会有child,这里的cancelCtx包括WithCancel、WithDeadline、WithTimeout。

多次cancel得到的err是一样的。

context创建流程图
context
WithDeadline
// context/context.go
type timerCtx struct {
 cancelCtx
 timer *time.Timer // Under cancelCtx.mu.
 deadline time.Time
}
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
 // 当parent为timerCtx 而且 deadline也比parent晚。那么就以WithCancel类型加入parent。
 // 因为child没有机会等到deadline。(timerCtx就比cancelCtx多了个时间)
 if cur, ok := parent.Deadline(); ok && cur.Before(d) {
 // The current deadline is already sooner than the new one.
 return WithCancel(parent)
 }
 c := &timerCtx{
 cancelCtx: newCancelCtx(parent),
 deadline: d,
 }
 propagateCancel(parent, c)
 dur := time.Until(d)
 // 早于当前时间, 说明已经过了deadline, 直接执行cancel
 if dur <= 0 {
 c.cancel(true, DeadlineExceeded) // deadline has already passed
 return c, func() { c.cancel(true, Canceled) }
 }
 c.mu.Lock()
 defer c.mu.Unlock()
 if c.err == nil {
 // 经过dur后自动执行cancel
 c.timer = time.AfterFunc(dur, func() {
 c.cancel(true, DeadlineExceeded)
 })
 }
 return c, func() { c.cancel(true, Canceled) }
}
// ok 为固定的true
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
 return c.deadline, true
}

流程见上图

timerCtx其实就在cancelCtx上面添加了个时间,它还是属于cancelCtx。

WithTimeout
// context/context.go
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
 // WithTimeout其实也就是WithDeadline
 return WithDeadline(parent, time.Now().Add(timeout))
}

WithTimeout其实调用的就是WithDeadline

WithValue
// context/context.go
type valueCtx struct {
 Context
 key, val interface{}
}
func WithValue(parent Context, key, val interface{}) Context {
 if key == nil {
 panic("nil key")
 }
 if !reflect.TypeOf(key).Comparable() {
 panic("key is not comparable")
 }
 // 只是简单存储 k v
 return &valueCtx{parent, key, val}
}
func (c *valueCtx) Value(key interface{}) interface{} {
 if c.key == key {
 return c.val
 }
 // 如果本地获取不到会到parent中获取
 return c.Context.Value(key)
}

只是简单存储了K、V

<-ctx.Done()
全文没看见对cancelCtx的done写消息, 那为什么在cancel的时候select{ case <-ctx.Done(): }没有堵塞呢?
func init() {
 close(closedchan)
}
func (c *cancelCtx) Done() <-chan struct{} {
 c.mu.Lock()
 if c.done == nil {
 c.done = make(chan struct{})
 }
 d := c.done
 c.mu.Unlock()
 return d
}
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
 
 ...
 if c.done == nil {
 c.done = closedchan
 } else {
 close(c.done) //关闭channel
 }
 ...
}

closedchan在init时候就进行了close。cancel的时候c.done总是处于close状态。
如果先<-ctx.Done(), 那么堵塞
如果先cancel, 那么<-ctx.Done()可执行, 并且返回的是false,说明chan是关闭的

结尾: context的代码整体来说还是比较易读的,但我肯定是写不出来啦~~~~


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:ihornet

查看原文:golang源码学习之context

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1205 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏