Golang併發模型之Context詳解

 

對於 Golang 開發者來講context(上下文)包必定不會陌生。但不少時候,咱們懶惰的只是見過它,或能起到什麼做用,並不會去深究它。css

應用場景:在 Go http 包的 Server 中,每個請求在都有一個對應的goroutine去處理。請求處理函數一般會啓動額外的goroutine用來訪問後端服務,好比數據庫和 RPC 服務。用來處理一個請求的goroutine一般須要訪問一些與請求特定的數據,好比終端用戶的身份認證信息、驗證相關的 token、請求的截止時間。當一個請求被取消或超時時,全部用來處理該請求的goroutine都應該迅速退出,而後系統才能釋放這些goroutine佔用的資源,官方博客golang

注意:go1.6及以前版本請使用golang.org/x/net/contextgo1.7及以後已移到標準庫contextsql

Context 原理

Context 的調用應該是鏈式的,經過WithCancelWithDeadlineWithTimeoutWithValue派生出新的 Context。當父 Context 被取消時,其派生的全部 Context 都將取消。數據庫

經過context.WithXXX都將返回新的 Context 和 CancelFunc。調用 CancelFunc 將取消子代,移除父代對子代的引用,而且中止全部定時器。未能調用 CancelFunc 將泄漏子代,直到父代被取消或定時器觸發。go vet工具檢查全部流程控制路徑上使用 CancelFuncs。swift

遵循規則

遵循如下規則,以保持包之間的接口一致,並啓用靜態分析工具以檢查上下文傳播。segmentfault

  1. 不要將 Contexts 放入結構體,相反context應該做爲第一個參數傳入,命名爲ctx。 func DoSomething(ctx context.Context,arg Arg)error { // ... use ctx ... }
  2. 即便函數容許,也不要傳入nil的 Context。若是不知道用哪一種 Context,可使用context.TODO()
  3. 使用context的Value相關方法只應該用於在程序和接口中傳遞的和請求相關的元數據,不要用它來傳遞一些可選的參數
  4. 相同的 Context 能夠傳遞給在不一樣的goroutine;Context 是併發安全的。

Context 包

Context 結構體。後端

// A Context carries a deadline, cancelation signal, and request-scoped values
// across API boundaries. Its methods are safe for simultaneous use by multiple // goroutines. type Context interface { // Done returns a channel that is closed when this Context is canceled // or times out. Done() <-chan struct{} // Err indicates why this context was canceled, after the Done channel // is closed. Err() error // Deadline returns the time when this Context will be canceled, if any. Deadline() (deadline time.Time, ok bool) // Value returns the value associated with key or nil if none. Value(key interface{}) interface{} } 
  • Done(),返回一個channel。當times out或者調用cancel方法時,將會close掉。
  • Err(),返回一個錯誤。該context爲何被取消掉。
  • Deadline(),返回截止時間和ok。
  • Value(),返回值。

全部方法安全

func Background() Context func TODO() Context func WithCancel(parent Context) (ctx Context, cancel CancelFunc) func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) func WithValue(parent Context, key, val interface{}) Context 

上面能夠看到Context是一個接口,想要使用就得實現其方法。在context包內部已經爲咱們實現好了兩個空的Context,能夠經過調用Background()和TODO()方法獲取。通常的將它們做爲Context的根,往下派生。併發

WithCancel 例子

WithCancel 以一個新的 Done channel 返回一個父 Context 的拷貝。app

229 func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { 230 c := newCancelCtx(parent) 231 propagateCancel(parent, &c) 232 return &c, func() { c.cancel(true, Canceled) } 233 } 234 235 // newCancelCtx returns an initialized cancelCtx. 236 func newCancelCtx(parent Context) cancelCtx { 237 return cancelCtx{ 238 Context: parent, 239 done: make(chan struct{}), 240 } 241 } 

此示例演示使用一個可取消的上下文,以防止 goroutine 泄漏。示例函數結束時,defer 調用 cancel 方法,gen goroutine 將返回而不泄漏。

package main import ( "context" "fmt" ) func main() { // gen generates integers in a separate goroutine and // sends them to the returned channel. // The callers of gen need to cancel the context once // they are done consuming generated integers not to leak // the internal goroutine started by gen. gen := func(ctx context.Context) <-chan int { dst := make(chan int) n := 1 go func() { for { select { case <-ctx.Done(): return // returning not to leak the goroutine case dst <- n: n++ } } }() return dst } ctx, cancel := context.WithCancel(context.Background()) defer cancel() // cancel when we are finished consuming integers for n := range gen(ctx) { fmt.Println(n) if n == 5 { break } } } 

WithDeadline 例子

369 func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { 370 if cur, ok := parent.Deadline(); ok && cur.Before(deadline) { 371 // The current deadline is already sooner than the new one. 372 return WithCancel(parent) 373 } 374 c := &timerCtx{ 375 cancelCtx: newCancelCtx(parent), 376 deadline: deadline, 377 } ...... 

能夠清晰的看到,當派生出的子 Context 的deadline在父Context以後,直接返回了一個父Context的拷貝。故語義上等效爲父。

WithDeadline 的最後期限調整爲不晚於 d 返回父上下文的副本。若是父母的截止日期已經早於 d,WithDeadline (父,d) 是在語義上等效爲父。返回的上下文完成的通道關閉的最後期限期滿後,返回的取消函數調用時,或當父上下文完成的通道關閉,以先發生者爲準。

看看官方例子:

package main import ( "context" "fmt" "time" ) func main() { d := time.Now().Add(50 * time.Millisecond) ctx, cancel := context.WithDeadline(context.Background(), d) // Even though ctx will be expired, it is good practice to call its // cancelation function in any case. Failure to do so may keep the // context and its parent alive longer than necessary. defer cancel() select { case <-time.After(1 * time.Second): fmt.Println("overslept") case <-ctx.Done(): fmt.Println(ctx.Err()) } } 

WithTimeout 例子

WithTimeout 返回 WithDeadline(parent, time.Now().Add(timeout))。

436 func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { 437 return WithDeadline(parent, time.Now().Add(timeout)) 438 } 

看看官方例子:

package main import ( "context" "fmt" "time" ) func main() { // Pass a context with a timeout to tell a blocking function that it // should abandon its work after the timeout elapses. ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) defer cancel() select { case <-time.After(1 * time.Second): fmt.Println("overslept") case <-ctx.Done(): fmt.Println(ctx.Err()) // prints "context deadline exceeded" } } 

WithValue 例子

454 func WithValue(parent Context, key, val interface{}) Context { 454 if key == nil { 455 panic("nil key") 456 } 457 if !reflect.TypeOf(key).Comparable() { 458 panic("key is not comparable") 459 } 460 return &valueCtx{parent, key, val} 461 } 

WithValue 返回的父與鍵關聯的值在 val 的副本。

使用上下文值僅爲過渡進程和 Api 的請求範圍的數據,而不是將可選參數傳遞給函數。

提供的鍵必須是可比性和應該不是字符串類型或任何其餘內置的類型以免包使用的上下文之間的碰撞。WithValue 用戶應該定義本身的鍵的類型。爲了不分配分配給接口 {} 時,上下文鍵常常有具體類型結構 {}。另外,導出的上下文關鍵變量靜態類型應該是一個指針或接口。

看看官方例子:

package main import ( "context" "fmt" ) func main() { type favContextKey string f := func(ctx context.Context, k favContextKey) { if v := ctx.Value(k); v != nil { fmt.Println("found value:", v) return } fmt.Println("key not found:", k) } k := favContextKey("language") ctx := context.WithValue(context.Background(), k, "Go") f(ctx, k) f(ctx, favContextKey("color")) } 

參考鏈接

[1] http://www.javashuo.com/article/p-ekinwxkp-ex.html
[2] http://www.01happy.com/golang-context-reading/

 

 

 

個人博客即將入駐「雲棲社區」,誠邀技術同仁一同入駐。

相關文章
相關標籤/搜索