Golang Context 详细介绍

Golang context

  本文包含对context实现上的分析和使用方式,分析部分源码讲解比价多,可能会比较枯燥,读者可以直接跳过去阅读使用部分。

  ps: 作者本着开源分享的精神撰写本篇文章,如果出现任何误差务必留言指正,作者会在第一时间内修正,共同维护一个好的开源生态,谢谢!!!

一、简介

作者所讲的context的包名称是: "golang.org/x/net/context" ,希望读者不要引用错误了。

在godoc中对context的介绍如下:

1 Package context defines the Context type, which carries deadlines, cancelation signals, and other request-scoped values across API boundaries and between processes. 2 3 As of Go 1.7 this package is available in the standard library under the name context. https://golang.org/pkg/context. 4 5Incoming requests to a server should create a Context, and outgoing calls to servers should accept a Context. The chain of function calls between must propagate the Context, optionally replacing it with a modified copy created using WithDeadline, WithTimeout, WithCancel, or WithValue. 6 7Programs that use Contexts should follow these rules to keep interfaces consistent across packages and enable static analysis tools to check context propagation: 8 9Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. The Context should be the first parameter, typically named ctx: 10 11func DoSomething(ctx context.Context, arg Arg) error { 12 // ... use ctx ... 13} 14 15Do not pass a nil Context, even if a function permits it. Pass context.TODO if you are unsure about which Context to use. 16 17Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions. 18 19The same Context may be passed to functions running in different goroutines; Contexts are safe for simultaneous use by multiple goroutines. 20 21See http://blog.golang.org/context for example code for a server that uses Contexts.

View Code

 鉴于作者英文水平有限,在这里不进行对照翻译,以免误导读者。它的第一句已经介绍了它的作用了:一个贯穿API的边界和进程之间的context 类型,可以携带deadlines、cancel signals和其他信息。就如同它的中文翻译一样:上下文。在一个应用服务中会并行运行很多的goroutines或进程, 它们彼此之间或者是从属关系、竞争关系、互斥关系,不同的goroutines和进程进行交互的时候需要进行状态的切换和数据的同步,而这就是context包要支持的功能。

二、解析

context的接口定义如下:

   每一个接口都有详细的注释,这里就不重复了。 在context的源码中有以下几个结构体实现了Context Interface:

1// A Context carries a deadline, a cancelation signal, and other values across 2// API boundaries.Context's methods may be called by multiple goroutines simultaneously. 3type Context interface { 4 // Deadline returns the time when work done on behalf of this context 5 // should be canceled. Deadline returns ok==false when no deadline is 6 // set. Successive calls to Deadline return the same results. 7 Deadline() (deadline time.Time, ok bool) 8 // Done returns a channel that's closed when work done on behalf of this 9 // context should be canceled. Done may return nil if this context can 10 // never be canceled. Successive calls to Done return the same value. 11 Done() <-chan struct{} 12 // Err returns a non-nil error value after Done is closed. Err returns 13 // Canceled if the context was canceled or DeadlineExceeded if the 14 // context's deadline passed. No other values for Err are defined. 15 // After Done is closed, successive calls to Err return the same value. 16 Err() error 17 // Value returns the value associated with this context for key, or nil 18 // if no value is associated with key. Successive calls to Value with 19 // the same key returns the same result. 20 Value(key interface{}) interface{} 21}

2.1 empty context

1// An emptyCtx is never canceled, has no values, and has no deadline. It is not 2// struct{}, since vars of this type must have distinct addresses. 3type emptyCtx int 4 5func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { 6 return 7} 8 9func (*emptyCtx) Done() <-chan struct{} { 10 return nil 11} 12 13func (*emptyCtx) Err() error { 14 return nil 15} 16 17func (*emptyCtx) Value(key interface{}) interface{} { 18 return nil 19} 20 21func (e *emptyCtx) String() string { 22 switch e { 23 case background: 24 return "context.Background" 25 case todo: 26 return "context.TODO" 27 } 28 return "unknown empty Context" 29}

  这是一个空的ctx类型,每一个返回值都为空,它什么都功能都不具备,主要的作用是作为所有的context类型的起始点,**context.Background()**函数返回的就是这中类型的Context:

1var ( 2 background = new(emptyCtx) 3 todo = new(emptyCtx) 4) 5 6// Background returns a non-nil, empty Context. It is never canceled, has no 7// values, and has no deadline. It is typically used by the main function, 8// initialization, and tests, and as the top-level Context for incoming 9// requests. 10func Background() Context { 11 return background 12}

   empty context的作用作者下面会阐述。 

2.2 cancle context

1// A cancelCtx can be canceled. When canceled, it also cancels any children 2// that implement canceler. 3type cancelCtx struct { 4 Context 5 6 mu sync.Mutex // protects following fields 7 done chan struct{} // created lazily, closed by first cancel call 8 children map[canceler]struct{} // set to nil by the first cancel call 9 err error // set to non-nil by the first cancel call 10} 11 12func (c *cancelCtx) Done() <-chan struct{} { 13 c.mu.Lock() 14 if c.done == nil { 15 c.done = make(chan struct{}) 16 } 17 d := c.done 18 c.mu.Unlock() 19 return d 20} 21 22func (c *cancelCtx) Err() error { 23 c.mu.Lock() 24 defer c.mu.Unlock() 25 return c.err 26} 27 28func (c *cancelCtx) String() string { 29 return fmt.Sprintf("%v.WithCancel", c.Context) 30} 31 32// cancel closes c.done, cancels each of c's children, and, if 33// removeFromParent is true, removes c from its parent's children. 34func (c *cancelCtx) cancel(removeFromParent bool, err error) { 35 if err == nil { 36 panic("context: internal error: missing cancel error") 37 } 38 c.mu.Lock() 39 if c.err != nil { 40 c.mu.Unlock() 41 return // already canceled 42 } 43 c.err = err 44 if c.done == nil { 45 c.done = closedchan 46 } else { 47 close(c.done) 48 } 49 for child := range c.children { 50 // NOTE: acquiring the child's lock while holding parent's lock. 51 child.cancel(false, err) 52 } 53 c.children = nil 54 c.mu.Unlock() 55 56 if removeFromParent { 57 removeChild(c.Context, c) 58 } 59}

  cancelctx中的成员变量:

     `done chan struct{}`: 在调用Done()函数时会将该变量返回,这可以用在多goroutines之间进行状态同步;

`children map[canceler]struct{}`: 一个context可以被其他context 引用,被引用的context为parant,引用context为children,该变量包含所有的children context;

    `Context`:  关联了Context接口,实现了Done()和Err(),但是没有实现Value()和Deadline();

      cancel函数是一个受保护的函数,不能在外部进行调用。可以看到在执行这个函数的时候 done channel会被关闭掉,同时它会调用所有的children context的cancel函数,但是没有解除彼此的依赖关系。这实际上比较好理解,因为children context的生命周期是依赖与parant context的。同时它还要判断是否调用 removeChild(c.Context, c)函数将解除对parant context的引用关系。

   在context.WithCancel(parent Context) 函数中返回的就是cancelCtx;

2.3 timer context

1// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to 2// implement Done and Err. It implements cancel by stopping its timer then 3// delegating to cancelCtx.cancel. 4type timerCtx struct { 5 cancelCtx 6 timer *time.Timer // Under cancelCtx.mu. 7 deadline time.Time 8} 9 10func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { 11 return c.deadline, true 12} 13 14func (c *timerCtx) String() string { 15 return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, time.Until(c.deadline)) 16} 17 18func (c *timerCtx) cancel(removeFromParent bool, err error) { 19 c.cancelCtx.cancel(false, err) 20 if removeFromParent { 21 // Remove this timerCtx from its parent cancelCtx's children. 22 removeChild(c.cancelCtx.Context, c) 23 } 24 c.mu.Lock() 25 if c.timer != nil { 26 c.timer.Stop() 27 c.timer = nil 28 } 29 c.mu.Unlock() 30}

   timer context 中的成员变量:

    `cancelCtx`: timerCtx 关联了 canelCtx类型;

    `timer`:  一个定时器,用来设置超时的时间;

    `dealine`: 一个时间类型,用来记录死亡时间;

  cancel函数 :1)它会先触发c.cancelCtx的cancel操作,但没有解除c.cancelCtx与parentCtx的依赖关系。2)判断是否去解除自身对于parentCtx的依赖, 3)停止它的timer,这个时候计时就结束了;

  它只实现了Deadline()函数,值得注意的是timeCtx和其包含的cancleCtx 是依赖于同一个parentCtx的。

  在WithDeadline(parent Context, deadline time.Time)和WithTimeout(parent Context, timeout time.Duration)函数中返回的就是 timerCtx;

2.4 value context

1// A valueCtx carries a key-value pair. It implements Value for that key and 2// delegates all other calls to the embedded Context. 3type valueCtx struct { 4 Context 5 key, val interface{} 6} 7 8func (c *valueCtx) String() string { 9 return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val) 10} 11 12func (c *valueCtx) Value(key interface{}) interface{} { 13 if c.key == key { 14 return c.val 15 } 16 return c.Context.Value(key) 17}

   value context成员变量:

    Context:它关联了Context接口

    key,val interface{} :两个变量形成一个key-value结构;

   它只实现了Value()函数,可以返回key对应的value,这里需要注意的是**,在查询value的时候,如果当前context中没有,会向上级的context进行搜索,递归查询**。

  WithValue(parent Context, key, val interface{}) 函数返回的即为value context。

2.5 总结

  综上四种类型的context 总结如下:

Name

Deadline

Done

Err

Value

继承

empty

nil

cancel

-

-

Context

timer

-

-

-

canelCtx

value

-

-

-

Context

  我们可以发现除了empty以外其他三种类型都没有完全去实现Context接口定义的所有函数,如果直接实例化一个cancelCtx对象,但是没有对 Context部分进行赋值,当调用其Value和Deadline函数会崩溃,timerCtx和valueCtx也是同样的道理。请读者一定要记住以上四种类型,这样你会很容易理解下面的内容。

 三、 Context的使用

3.1 Context 常用函数

 我们在上面的介绍过程中提到了很多函数:

1//创建一个Cancel contextfunc WithCancel(parent Context) (ctx Context, cancel CancelFunc) 2//创建一个带有 deadline的Timer context 3func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) 4//创建一个带有超时的Timer context 5func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) 6//创建一个Value context 7func WithValue(parent Context, key, val interface{}) Context

   这些函数都是在使用Context中经常用到的,我们接下来说明它们的功能。

WithCancel

介绍:

复制parentCtx,同时创建一个新的Done Channel返回Cancel函数,当以下两种情况发生时会关闭Done Channel,触发Done信号:

  1) 返回的Cancel函数被调用;

  2) parentCtx触发了Done信号;

  通常一个Context生命周期截止了(不再被需要的时候)就要立刻调用Cancel函数

1func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { 2 c := newCancelCtx(parent) 3 propagateCancel(parent, &c) 4 return &c, func() { c.cancel(true, Canceled) } 5}

   在newcancel函数中实例化一个cancel context对象:  

1// newCancelCtx returns an initialized cancelCtx. 2func newCancelCtx(parent Context) cancelCtx { 3 return cancelCtx{Context: parent} 4}

   propagateCancel如注释所述:向上找到最近的可以被依赖的父context,将子context放入 parent的children队列;如果找不到就开一个goroutines来等待主链退出的通知。

1// propagateCancel arranges for child to be canceled when parent is. 2func propagateCancel(parent Context, child canceler) { 3 if parent.Done() == nil { 4 return // parent is never canceled 5 } 6 if p, ok := parentCancelCtx(parent); ok { 7 p.mu.Lock() 8 if p.err != nil { 9 // parent has already been canceled 10 child.cancel(false, p.err) 11 } else { 12 if p.children == nil { 13 p.children = make(map[canceler]struct{}) 14 } 15 p.children[child] = struct{}{} 16 } 17 p.mu.Unlock() 18 } else { 19 go func() { 20 select { 21 case <-parent.Done(): 22 child.cancel(false, parent.Err()) 23 case <-child.Done(): 24 } 25 }() 26 } 27}

  parentCancelCtx : 寻找沿着parant引用向上追溯,直到发现一个cancelCtx;

1// parentCancelCtx follows a chain of parent references until it finds a 2// *cancelCtx. This function understands how each of the concrete types in this 3// package represents its parent. 4func parentCancelCtx(parent Context) (*cancelCtx, bool) { 5 for { 6 switch c := parent.(type) { 7 case *cancelCtx: 8 return c, true 9 case *timerCtx: 10 return &c.cancelCtx, true 11 case *valueCtx: 12 parent = c.Context 13 default: 14 return nil, false 15 } 16 } 17}

   它还返回一个函数指针,这个函数指针实际上就是执行cancelCtx中的cancel函数--c.cancel(true, Canceled),该操作会解除和parent ctx的依赖。

   总的来说创建一个新的context就是在parant context中挂载一个 children context,也许传入的parent与新生成的ctx会挂载到同一个ctx下,也许会加入到parent contxt的children 队列中。我们要与上面的四种类型的context比较,empty context和 value context是不具备挂载children的能力的,而cancel context 和timer context 两种类型具备挂载chidren 的能力(实际上timerCtx的挂载能力是继承于canelCtx)。

  但问题来了,在创建cancel context时候需要传入一个parent 参数,那么这个parent从哪里来?这时候就需要 func Background() Context 这个函数,它返回一个作为起始点的context对象,而这个BackgroundCtx是一个empty context,这就是empty context的作用。在回想一下上面的介绍是不是很合理的构造?

WithDeadline

复制parentCtx,在创建的时候如果parentCtx已经触发Deadline,则直接返回一个cancelCtx和Cancel函数,否则会将返回一个timeCtx和Cancel函数。在发生以下情况的时候Done信号会被触发:

1)Cancel函数被调用

2)当前时间超过设置的Deadline

3)parentCtx触发了Done信号

1func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { 2 if cur, ok := parent.Deadline(); ok && cur.Before(d) { 3 // The current deadline is already sooner than the new one. 4 return WithCancel(parent) 5 } 6 c := &timerCtx{ 7 cancelCtx: newCancelCtx(parent), 8 deadline: d, 9 } 10 propagateCancel(parent, c) 11 dur := time.Until(d) 12 if dur <= 0 { 13 c.cancel(true, DeadlineExceeded) // deadline has already passed 14 return c, func() { c.cancel(true, Canceled) } 15 } 16 c.mu.Lock() 17 defer c.mu.Unlock() 18 if c.err == nil { 19 c.timer = time.AfterFunc(dur, func() { 20 c.cancel(true, DeadlineExceeded) 21 }) 22 } 23 return c, func() { c.cancel(true, Canceled) } 24}

WithValue

  复制parentCtx,保存传入的key-value键值对,没有Cancel函数被返回,但不代表该context没有done信号,只有在parentCtx的Done信号被触发的时候,才会发送。

1func WithValue(parent Context, key, val interface{}) Context { 2 if key == nil { 3 panic("nil key") 4 } 5 if !reflect.TypeOf(key).Comparable() { 6 panic("key is not comparable") 7 } 8 return &valueCtx{parent, key, val} 9}

 3.2 、Context的应用示例

参考网址   

[1] https://godoc.org/golang.org/x/net/context

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

java将前端的json数组字符串转换为列表

记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang