本文共 6460 字,大约阅读时间需要 21 分钟。
自从接触Go语言以来,Context就一直在用,不过一直弄不清楚这东西究竟有啥用,平常都是CV代码,最多就是稀里糊涂用个Background()。不过最近在开发Golang反向代理的时候遇到一个传值问题,因为调用的是底层的库不支持传递变量,所以甚是困惑,后来在stackoverflow搜索了一番,发现已有类似问题,给出的答案很简单,就是通过Context的WithValue传值,而且底层库自身的参数也包含Context,所以困惑的问题一下就解决了。回过头来想一下,如此简单明显的问题都不会,说明对Golang了解的过于片面,这可不是一个优秀工程师应有的表现。然后就看了下Context包的源码。
Context包其实写的挺简单的,代码量也不多,很适合阅读。看完之后给我感觉就是Context就像一个共享内存,在不同的goroutine之间共享一些信息,比如值、或者信号等等,设计比较简洁,代码也比较有参考价值。
下面直接看代码:
package contextimport ( "errors" "internal/reflectlite" "sync" "sync/atomic" "time")type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{}}var Canceled = errors.New("context canceled")var DeadlineExceeded errortype emptyCtx struct{}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" default: return "unknown empty Context" }}var ( background = new(emptyCtx) todo = new(emptyCtx))func Background() Context { return background}func TODO() Context { return todo}type CancelFunc func()func WithCancel(parent Context) (Context, CancelFunc) { if parent == nil { panic("cannot create context from nil parent") } c := newCancelCtx(parent) propagateCancel(parent, c) return c, func() { c.cancel(true, Canceled) }}type cancelCtx struct { Context *emptyCtx mu sync.Mutex done chan struct{} err error children map[canceler]struct{}}func newCancelCtx(parent Context) *cancelCtx { return &cancelCtx{ Context: parent, mu: sync.Mutex{}, done: nil, err: nil, children: make(map[canceler]struct{}), }}func propagateCancel(parent Context, child *cancelCtx) { done := parent.Done() if done == nil { return } select { case <-done: child.cancel(false, parent.Err()) return default: } p, ok := parentCancelCtx(parent) if !ok { atomic.AddInt32(&goroutines, +1) go func() { select { case <-parent.Done(): child.cancel(false, parent.Err()) case <-child.Done(): } }() return } p.mu.Lock() if p.err != nil { child.cancel(false, p.err) } else { if p.children == nil { p.children = make(map[canceler]struct{}) } p.children[child] = struct{}{} } p.mu.Unlock()}func parentCancelCtx(parent Context) (*cancelCtx, bool) { done := parent.Done() if done == closedchan || done == nil { return nil, false } p, ok := parent.Value(&cancelCtxKey).(*cancelCtx) if !ok { return nil, false } p.mu.Lock() ok = p.done == done p.mu.Unlock() if !ok { return nil, false } return p, true}func removeChild(parent Context, child canceler) { p, ok := parentCancelCtx(parent) if !ok { return } p.mu.Lock() if p.children != nil { delete(p.children, child) } p.mu.Unlock()}type canceler interface { cancel(removeFromParent bool, err error) Done() <-chan struct{}}var closedchan = make(chan struct{})func init() { close(closedchan)}func (c *cancelCtx) Value(key interface{}) interface{} { if key == &cancelCtxKey { return c } return c.Context.Value(key)}func (c *cancelCtx) Done() <-chan struct{} { c.mu.Lock() if c.done == nil { c.done = closedchan } else { close(c.done) } c.mu.Unlock() return c.done}func (c *cancelCtx) Err() error { c.mu.Lock() err := c.err c.mu.Unlock() return err}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 } c.err = err if c.done == nil { c.done = closedchan } else { close(c.done) } for child := range c.children { child.cancel(false, err) } c.children = nil c.mu.Unlock() if removeFromParent { removeChild(c.Context, c) }}type timerCtx struct { cancelCtx: *cancelCtx deadline time.Time}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() + "])"}func (c *timerCtx) cancel(removeFromParent bool, err error) { c.cancelCtx.cancel(false, err) if removeFromParent { removeChild(c.cancelCtx.Context, c) } c.mu.Lock() if c.timer != nil { c.timer.Stop() c.timer = nil } c.mu.Unlock()}func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { if parent == nil { panic("cannot create context from nil parent") } if cur, ok := parent.Deadline(); ok && cur.Before(d) { return WithCancel(parent) } c := &timerCtx{ cancelCtx: newCancelCtx(parent), deadline: d, } propagateCancel(parent, c) if d <= time.Until(d) { 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(d, func() { c.cancel(true, DeadlineExceeded) }) } return c, func() { c.cancel(true, Canceled) }}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"}type valueCtx struct { Context key, val interface{}}func WithValue(parent Context, key, val interface{}) Context { if parent == nil { panic("cannot create context from nil parent") } if key == nil { panic("nil key") } if !reflectlite.TypeOf(key).Comparable() { panic("key is not comparable") } return &valueCtx{ Context: parent, key: key, val: val, }}func (c *valueCtx) String() string { return contextName(c.Context) + ".WithValue(type " + reflectlite.TypeOf(c.key).String() + ", val " + stringify(c.val) + ")"}func stringify(v interface{}) string { switch s := v.(type) { case stringer: return s.String() case string: return s } return "\""}func (c *valueCtx) Value(key interface{}) interface{} { if c.key == key { return c.val } return c.Context.Value(key)}
Context包定义了两个私有指针变量background和todo,这两者都是emptyCtx的实例。Background()返回background,TODO()返回todo。自定义取消函数类型CancelFunc用于停止当前工作,且无需等待。WithCancel方法返回一个cancelCtx和一个取消函数,cancelCtx是Context的核心结构,用于管理子Context的生命周期。
通过阅读Context包的源码,我深刻理解了Context的设计理念。它不仅仅是一个传值机制,更是一个管理协程生命周期、传递信号和截止时间的强大工具。Context的设计简洁高效,适合复杂的并发编程环境。
了解了这些之后,我对Golang的Context功能有了更深入的认识。它在实际应用中能够有效地解决并发编程中的通信和资源管理问题,是一个非常实用的工具。通过实践和深入研究,我相信自己能够更好地利用Context来优化日常开发中的代码结构,提高程序的效率和可维护性。
转载地址:http://ybgfk.baihongyu.com/