源码看 golang context
2021/11/5 22:13:43
本文主要是介绍源码看 golang context,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
Golang context
Context 是golang中的上下文,用于服务器处理请求相关的协程之间,处理其中的超时中断,取消操作等情况。
来自源码的解释:
对服务器的传入请求应该创建一个上下文,而对服务器的传出调用应该接受一个上下文。 它们之间的函数调用链必须传播 Context,可以选择将其替换为使用 WithCancel、WithDeadline、WithTimeout 或 WithValue 创建的派生 Context。 当一个上下文被取消时,从它派生的所有上下文也被取消。
WithCancel、WithDeadline 和 WithTimeout 函数采用 Context(父级)并返回派生的 Context(子级)和 CancelFunc。调用 CancelFunc 会取消子项及其子项,删除父项对子项的引用,并停止任何关联的计时器。未能调用 CancelFunc 会泄漏子项及其子项,直到父项被取消或计时器触发。 go vet 工具检查 CancelFuncs 是否用于所有控制流路径。
不要将 Context 存储在结构类型中; 相反,将 Context 显式传递给需要它的每个函数。 Context 应该是第一个参数,通常命名为 ctx:
func DoSomething(ctx context.Context, arg Arg) 错误 { // ... 使用 ctx ... }
相同的 Context 可以传递给运行在不同 goroutine 中的函数; Context 对于多个 goroutine 同时使用是安全的。
Context接口:
type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} }
Context 是接口类型,明确了各种context应实现的方法。
Deadline() 返回context的任务取消截至时间,如果没有设定,ok 返回false。
Done() 返回一个chan, 如果context不会被取消,返回nil,否则,当context取消时,可以从done返回的chan中读取数据。
Err(), 如果done被关闭,则返回非空err,说明取消原因,否则返回nil。
Value() 可以在协程间共享一些kv数据,通过Value()获取值。 使用 value只用于request范围内相关的数据, 而不是为了做函数的可选参数。
代码大纲
Context的代码非常少,结构也非常简单
Context.Err()返回的两种错误
// 主动取消,用于cancelCtx var Canceled = errors.New("context canceled") // 超时取消,用于timerCtx var DeadlineExceeded error = deadlineExceededError{} type deadlineExceededError struct{} func (deadlineExceededError) Error() string { return "context deadline exceeded" } func (deadlineExceededError) Timeout() bool { return true } func (deadlineExceededError) Temporary() bool { return true }
四种Context
实现了Context接口的有以下四种context:
// 是空的contex type emptyCtx int // 可主动取消的Context type cancelCtx struct {} // 可定时取消的Context type timerCtx struct {} // 可传递key,value的Context type valueCtx struct {}
空Context
var ( background = new(emptyCtx) todo = new(emptyCtx) ) func Background() Context { return background } func TODO() Context { return todo }
emptyCtx 是空 context, Background() 和TODO() 分别返回一个emptyCtx,
Background() 通常用于主函数,初始化,以及测试,作为top-level context。
TODO() 用于某个函数需要传递context,而你不知道传递什么时候。
即使函数允许,也不要传递 nil Context。 如果您不确定要使用哪个 Context,请传递 context.TODO。
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 除实现Context接口的四个函数外,还实现了可被fmt.Print()打印的String()函数。
cancelCtx
cancelCtx 是一个可被取消的context, 当被取消时,它会取消它的所有子context。
type cancelCtx struct { Context // 用Context来表示父节点 mu sync.Mutex // protects following fields done chan struct{} // created lazily, closed by first cancel call children map[canceler]struct{} // 用于在取消时,取消所有子context err error // set to non-nil by the first cancel call } func (c *cancelCtx) Value(key interface{}) interface{} { ... } 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 }
Done() 返回一个chan,当 cancelCtx 取消时,此chan会被关闭,所以chan接收端会接收到nil数据,从而得知 cancelCtx 被取消。
func (c *cancelCtx) Err() error { ... } type stringer interface { String() string } func contextName(c Context) string { if s, ok := c.(stringer); ok { return s.String() } return reflectlite.TypeOf(c).String() } func (c *cancelCtx) String() string { return contextName(c.Context) + ".WithCancel" }
和 emptyCtx 一样,也是多实现了一个String()方法
WithCancel
通过WithCancel可以创建一个cancelCtx, 同时还会返回一个cancel函数,cancel函数会关闭cancelCtx.done, 也会cancel每一个子context, 同时还会从父 context中移除自己。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { c := newCancelCtx(parent) propagateCancel(parent, &c) return &c, func() { c.cancel(true, Canceled) } } func newCancelCtx(parent Context) cancelCtx { return cancelCtx{Context: parent} } // 使child在parent取消时被取消 func propagateCancel(parent Context, child canceler) { done := parent.Done() if done == nil { return // parent永远不会被取消 } select { case <-done: // parent 已经被取消 child.cancel(false, parent.Err()) return default: } if p, ok := parentCancelCtx(parent); ok { p.mu.Lock() if p.err != nil { // parent 已经被取消 child.cancel(false, p.err) } else { // 加入到parent的 children中 if p.children == nil { p.children = make(map[canceler]struct{}) } p.children[child] = struct{}{} } p.mu.Unlock() } else { // 祖先没有 cancelCtx atomic.AddInt32(&goroutines, +1) // 启动协程,为parent善后 go func() { select { // 源码此处没有注释,个人理解,虽然祖先没有cancelCtx,但不意味着Done()没有返回管道,因为如果Context是可以用户定义的,所有chlid也需要检测用户自定义的Done()。 case <-parent.Done(): // 因为祖先没有cancelCtx, 所以不需要从parent中移除自己,所以传递false child.cancel(false, parent.Err()) case <-child.Done(): // 这个的原因是,如果child被取消了,那么此协程会发送资源泄露,所有child的Done也要检测 } }() } } func parentCancelCtx(parent Context) (*cancelCtx, bool) { done := parent.Done() if done == closedchan || done == nil { return nil, false } p, ok := parent.Value(&cancelCtxKey).(*cancelCtx) // 如果祖先节点存在一个cancelCtx, (timerCtx也是cancelCtx) if !ok { return nil, false } p.mu.Lock() ok = p.done == done p.mu.Unlock() if !ok { return nil, false } return p, true }
propagateCancel(parent Context, child canceler) 用于建立父子关系,以至于某一个祖先 context取消时,能够取消自己。
其中 parentCancelCtx(parent Context) (*cancelCtx, bool) 会找到一个为cancelCtx的祖先节点,如果没有的话,返回false。
高级处:cancelCtx的Value会对key进行判断,所以查找第一个为cancelCtx的祖先节点,parent.Value(&cancelCtxKey).(*cancelCtx) 即可以做到。
func (c *cancelCtx) Value(key interface{}) interface{} { if key == &cancelCtxKey { return c } return c.Context.Value(key) } p, ok := parent.Value(&cancelCtxKey).(*cancelCtx) if !ok { return nil, false }
cancel
cancel函数取消此cancelCtx, 同时也会调用所有子context的cancel函数,并将自己与parent解除父子关系
func (c *cancelCtx) cancel(removeFromParent bool, err error) { if err == nil { panic("context: internal error: missing cancel error") } c.mu.Lock()] // 已经cancel if c.err != nil { c.mu.Unlock() return // already canceled } c.err = err // 关闭c.done if c.done == nil { /* * var closedchan = make(chan struct{}) * * func init() { * close(closedchan) * } */ c.done = closedchan } else { close(c.done) } // 遍历关闭子 context for child := range c.children { // NOTE: acquiring the child's lock while holding parent's lock. child.cancel(false, err) } // gc 子context c.children = nil c.mu.Unlock() // 从parent context中移除自己 if removeFromParent { removeChild(c.Context, c) } }
timerCtx
type timerCtx struct { cancelCtx timer *time.Timer // Under cancelCtx.mu. deadline time.Time }
timerCtx也是cancelCtx, 同时增加了用于定时的timer,以及标识过期时间的deadline。
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { return c.deadline, true } func (c *timerCtx) String() string { return contextName(c.cancelCtx.Context) + ".WithDeadline(" + c.deadline.String() + " [" + time.Until(c.deadline).String() + "])" }
WithDeadline
根据父 parent,创建一个可取消的 context.
如果设置的取消时间晚于parent的deadline,那么直接与parent关联即可,因为parent取消时会取消自己。
否则设置deadline为指定的取消时间。
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { if cur, ok := parent.Deadline(); ok && cur.Before(d) { // parent 的deadline早于指定的d return WithCancel(parent) } c := &timerCtx{ cancelCtx: newCancelCtx(parent), deadline: d, } // 建立与祖先之间取消关系 propagateCancel(parent, c) dur := time.Until(d) if dur <= 0 { c.cancel(true, DeadlineExceeded) // 已经到了取消时间 return c, func() { c.cancel(false, Canceled) } } c.mu.Lock() defer c.mu.Unlock() if c.err == nil { // 设置过期取消函数,并定时 c.timer = time.AfterFunc(dur, func() { c.cancel(true, DeadlineExceeded) }) } return c, func() { c.cancel(true, Canceled) } }
WithTimeout
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { return WithDeadline(parent, time.Now().Add(timeout)) }
cancel
func (c *timerCtx) cancel(removeFromParent bool, err error) { // 执行cancelCtx的cancel,循环取消子context c.cancelCtx.cancel(false, err) if removeFromParent { // 从parent从移除自己 removeChild(c.cancelCtx.Context, c) } c.mu.Lock() if c.timer != nil { c.timer.Stop() c.timer = nil } c.mu.Unlock() }
valueCtx
type valueCtx struct { Context key, val interface{} }
多了一对kv。valueCtx不是cancelCtx,不可取消,没有deadline, done()等
WithValue
func WithValue(parent Context, key, val interface{}) Context { if key == nil { panic("nil key") } if !reflectlite.TypeOf(key).Comparable() { panic("key is not comparable") } return &valueCtx{parent, key, val} }
WithValue用于创建一个附带数据的valueCtx。由于valueCtx的Context是父节点,所以假设祖先节点有valueCtx,那么valueCtx的Value(key interface{})也可以找到祖先节点的key,value
func stringify(v interface{}) string { switch s := v.(type) { case stringer: return s.String() case string: return s } return "<not Stringer>" } func (c *valueCtx) String() string { return contextName(c.Context) + ".WithValue(type " + reflectlite.TypeOf(c.key).String() + ", val " + stringify(c.val) + ")" }
String()函数
func (c *valueCtx) Value(key interface{}) interface{} { if c.key == key { return c.val } return c.Context.Value(key) }
自下向上查找key对应的value,直到找到指定的key,否则找到根节点才返回
这篇关于源码看 golang context的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-24MongoDB资料:新手入门完全指南
- 2024-12-20go-zero 框架的 RPC 服务 启动start和停止 底层是怎么实现的?-icode9专业技术文章分享
- 2024-12-19Go-Zero 框架的 RPC 服务启动和停止的基本机制和过程是怎么实现的?-icode9专业技术文章分享
- 2024-12-18怎么在golang中使用gRPC测试mock数据?-icode9专业技术文章分享
- 2024-12-15掌握PageRank算法核心!你离Google优化高手只差一步!
- 2024-12-15GORM 中的标签 gorm:"index"是什么?-icode9专业技术文章分享
- 2024-12-11怎么在 Go 语言中获取 Open vSwitch (OVS) 的桥接信息(Bridge)?-icode9专业技术文章分享
- 2024-12-11怎么用Go 语言的库来与 Open vSwitch 进行交互?-icode9专业技术文章分享
- 2024-12-11怎么在 go-zero 项目中发送阿里云短信?-icode9专业技术文章分享
- 2024-12-11怎么使用阿里云 Go SDK (alibaba-cloud-sdk-go) 发送短信?-icode9专业技术文章分享