Golang context
本文包含對context實現上的分析和使用方式,分析部分源碼講解比價多,可能會比較枯燥,讀者可以直接跳過去閱讀使用部分。
ps: 作者本著開源分享的精神撰寫本篇文章,如果出現任何誤差務必留言指正,作者會在第一時間內修正,共同維護一個好的開源生態,謝謝!!!
一、簡介
作者所講的context的包名稱是: "golang.org/x/net/context" ,希望讀者不要引用錯誤了。
在godoc中對context的介紹如下:
Package context defines the Context type, which carries deadlines, cancelation signals, and other request-scoped values across API boundaries and between processes. As of Go 1.7 this package is available in the standard library under the name context. https://golang.org/pkg/context. Incoming 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.Programs that use Contexts should follow these rules to keep interfaces consistent across packages and enable static analysis tools to check context propagation:Do 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:func DoSomething(ctx context.Context, arg Arg) error { // ... use ctx ...}Do not pass a nil Context, even if a function permits it. Pass context.TODO if you are unsure about which Context to use.Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.The same Context may be passed to functions running in different goroutines; Contexts are safe for simultaneous use by multiple goroutines.See 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:
// A Context carries a deadline, a cancelation signal, and other values across// API boundaries.Context's methods may be called by multiple goroutines simultaneously.type Context interface { // Deadline returns the time when work done on behalf of this context // should be canceled. Deadline returns ok==false when no deadline is // set. Successive calls to Deadline return the same results. Deadline() (deadline time.Time, ok bool) // Done returns a channel that's closed when work done on behalf of this // context should be canceled. Done may return nil if this context can // never be canceled. Successive calls to Done return the same value. Done() <-chan struct{} // Err returns a non-nil error value after Done is closed. Err returns // Canceled if the context was canceled or DeadlineExceeded if the // context's deadline passed. No other values for Err are defined. // After Done is closed, successive calls to Err return the same value. Err() error // Value returns the value associated with this context for key, or nil // if no value is associated with key. Successive calls to Value with // the same key returns the same result. Value(key interface{}) interface{}}
2.1 empty context
// An emptyCtx is never canceled, has no values, and has no deadline. It is not// struct{}, since vars of this type must have distinct addresses.type emptyCtx intfunc (*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"}
這是一個空的ctx類型,每一個傳回值都為空白,它什麼都功能都不具備,主要的作用是作為所有的context類型的起始點,context.Background()函數返回的就是這中類型的Context:
var ( background = new(emptyCtx) todo = new(emptyCtx))// Background returns a non-nil, empty Context. It is never canceled, has no// values, and has no deadline. It is typically used by the main function,// initialization, and tests, and as the top-level Context for incoming// requests.func Background() Context { return background}
empty context的作用作者下面會闡述。
2.2 cancle context
// A cancelCtx can be canceled. When canceled, it also cancels any children// that implement canceler.type cancelCtx struct { Context mu sync.Mutex // protects following fields done chan struct{} // created lazily, closed by first cancel call children map[canceler]struct{} // set to nil by the first cancel call err error // set to non-nil by the first cancel call}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) Err() error { c.mu.Lock() defer c.mu.Unlock() return c.err}func (c *cancelCtx) String() string { return fmt.Sprintf("%v.WithCancel", c.Context)}// cancel closes c.done, cancels each of c's children, and, if// removeFromParent is true, removes c from its parent's children.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 // already canceled } 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) }}
cancelctx中的成員變數:
`done chan struct{}`: 在調用Done()函數時會將該變數返回,這可以用在多goroutines之間進行狀態同步;
`children map[canceler]struct{}`: 一個context可以被其他context 引用,被引用的context為parant,引用context為children,該變數包含所有的children context;
`Context`: 繼承了所有Context的介面,但是沒有實現Value()和Deadline();
cancel函數是一個受保護的函數,不能在外部進行調用,可以看到在執行這個函數的時候 done chain會被關閉掉,同時它會調用所有的children context的cancel函數。這實際上比較好理解,因為children context的生命週期是依賴與parant context的。同時它還要調用 removeChild(c.Context, c)函數將解除對parant context的參考關聯性。
在context.WithCancel(parent Context) 函數中返回的就是cancel context;
2.3 timer context
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to// implement Done and Err. It implements cancel by stopping its timer then// delegating to cancelCtx.cancel.type timerCtx struct {cancelCtxtimer *time.Timer // Under cancelCtx.mu.deadline time.Time}func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {return c.deadline, true}func (c *timerCtx) String() string {return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, time.Until(c.deadline))}func (c *timerCtx) cancel(removeFromParent bool, err error) {c.cancelCtx.cancel(false, err)if removeFromParent {// Remove this timerCtx from its parent cancelCtx's children.removeChild(c.cancelCtx.Context, c)}c.mu.Lock()if c.timer != nil {c.timer.Stop()c.timer = nil}c.mu.Unlock()}
timer context 中的成員變數:
`cancelCtx`: timercontext 繼承了 canel context類型,它繼承了cancel類型Context中所有的成員變數;
`timer`: 一個定時器,用來設定逾時的時間;
`dealine`: 一個時間類型,用來記錄死亡時間;
cancel函數 :重載了上面的cancel context的canel()函數,它會調用removeChild(c.cancelCtx.Context, c)去釋放所有依賴於它的children context, 然後它會去停止它的timer,這個時候計時就結束了;
它實現了String() 和 Deadline() 但是沒有實現Value(), 重載了cancel context的String()函數;
在WithDeadline(parent Context, deadline time.Time)和WithTimeout(parent Context, timeout time.Duration)函數中返回的就是 timer context;
2.4 value context
// A valueCtx carries a key-value pair. It implements Value for that key and// delegates all other calls to the embedded Context.type valueCtx struct {Contextkey, val interface{}}func (c *valueCtx) String() string {return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)}func (c *valueCtx) Value(key interface{}) interface{} {if c.key == key {return c.val}return c.Context.Value(key)}
value context成員變數:
Context:它繼承了Context的所有介面,但是沒有去實現它,所以要使用valueCtx必須要去賦值這個變數;
key,val interface{} :兩個變數形成一個key-value結構;
它實現了Value()介面,可以返回key對應的value。
WithValue(parent Context, key, val interface{}) 函數返回的即為value context。
2.5 總結
綜上四種類型的context 總結如下
Name |
Deadline |
Done |
Err |
Value |
繼承 |
empty |
+ |
+ |
+ |
+ |
nil |
cancel |
- |
+ |
+ |
- |
Context |
timer |
+ |
- |
- |
- |
canel |
value |
- |
- |
- |
+ |
Context |
我們可以發現除了empty以外其他三種類型都沒有完全去實現Context的全部介面,如果直接執行個體化一個cancel context對象,但是沒有對 Context部分進行賦值,當調用其Value和Deadline介面會崩潰,timer context和value也是同樣的道理。請讀者一定要記住以上四種類型,這樣你會很容易理解下面的內容。
三、 Context的使用
3.1 Context 常用函數
我們在上面的介紹過程中提到了很多函數:
//建立一個Cancel context
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
//建立一個帶有 deadline的Timer contextfunc WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
//建立一個帶有逾時的Timer contextfunc WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
//建立一個Value contextfunc WithValue(parent Context, key, val interface{}) Context
這些函數都是在使用Context中經常用到的,我們這裡就不對所有的函數進行解析了,只以WithCancel函數為例:
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {c := newCancelCtx(parent)propagateCancel(parent, &c)return &c, func() { c.cancel(true, Canceled) }}
在newcancel函數中執行個體化一個cancel context對象:
// newCancelCtx returns an initialized cancelCtx.func newCancelCtx(parent Context) cancelCtx {return cancelCtx{Context: parent}}
propagateCancel如注釋所述:向上找到最近的可以被取消的父context,將子context放入 parent的children隊列;如果找不到就開一個goroutines來等待主鏈退出的通知。
// propagateCancel arranges for child to be canceled when parent is.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 canceledchild.cancel(false, p.err)} else {if p.children == nil {p.children = make(map[canceler]struct{})}p.children[child] = struct{}{}}p.mu.Unlock()} else {go func() {select {case <-parent.Done():child.cancel(false, parent.Err())case <-child.Done():}}()}}
parentCancelCtx : 尋找沿著parant引用向上追溯,直到發現一個cancelCtx;
// parentCancelCtx follows a chain of parent references until it finds a// *cancelCtx. This function understands how each of the concrete types in this// package represents its parent.func parentCancelCtx(parent Context) (*cancelCtx, bool) { for { switch c := parent.(type) { case *cancelCtx: return c, true case *timerCtx: return &c.cancelCtx, true case *valueCtx: parent = c.Context default: return nil, false } }}
它還返回一個函數指標,這個函數指標實際上就是執行cancelCtx中的cancel函數。
總的來說建立一個新的context就是在parant context中掛載一個 children context,也許傳入的parent與新產生的ctx會掛載到同一個ctx下,也許會加入到parent contxt的children 隊列中。我們要與上面的四種類型的context比較,empty context和 value context是不具備掛載children的能力的,而cancel context 和timer context 兩種類型具備掛載chidren 的能力。
但問題來了,在建立cancel context時候需要傳入一個parent 參數,那麼這個parent從哪裡來?這時候就需要 func Background() Context 這個函數,它返回一個作為起始點的context對象,而這個BackgroundCtx是一個empty context,這就是empty context的作用。在回想一下上面的介紹是不是很合理的構造?
3.2 、Context的應用樣本
參考網址
[1] https://godoc.org/golang.org/x/net/context