Context
一般被譯做 上下文
,通常理解爲程序單元的一個運行狀態、現場、快照,而翻譯中 上下
又很好地詮釋了其本質,上下上下則是存在上下層的傳遞, 上
會把內容傳遞給 下
。golang
在Go語言中,程序單元也就指的是Goroutine。每一個Goroutine在執行以前,都要先知道程序當前的執行狀態,一般將這些執行狀態封裝在一個Context
變量中,傳遞給要執行的Goroutine中。上下文則幾乎已經成爲傳遞與請求同生存週期變量的標準方法。編程
context
包不只實現了在程序單元之間共享狀態變量的方法,同時能經過簡單的方法,使咱們在被調用程序單元的外部,經過設置ctx變量值,將過時或撤銷這些信號傳遞給被調用的程序單元。在網絡編程中,若存在A調用B的API, B再調用C的API,若A調用B取消,那也要取消B調用C,經過在A,B,C的API調用之間傳遞 Context
,以及判斷其狀態,就能解決此問題,這是爲何gRPC的接口中帶上 ctx context.Context
參數的緣由之一。安全
context
包的核心就是 Context
接口,其定義以下:網絡
type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} }
Deadline
會返回一個超時時間,Goroutine得到了超時時間後,例如能夠對某些io操做設定超時時間。函數
Done
方法返回一個信道(channel),當 Context
被撤銷或過時時,該信道是關閉的,即它是一個表示Context是否已關閉的信號。post
當 Done
信道關閉後, Err
方法代表 Contex
t被撤的緣由。ui
Value
可讓Goroutine共享一些數據,固然得到數據是協程安全的。但使用這些數據的時候要注意同步,好比返回了一個map,而這個map的讀寫則要加鎖。spa
Context
接口沒有提供方法來設置其值和過時時間,也沒有提供方法直接將其自身撤銷。也就是說, Context
不能改變和撤銷其自身。那麼該怎麼經過 Context
傳遞改變後的狀態呢?.net
不管是Goroutine,他們的建立和調用關係老是像層層調用進行的,就像人的輩分同樣,而更靠頂部的Goroutine應有辦法主動關閉其下屬的Goroutine的執行(否則程序可能就失控了)。爲了實現這種關係,Context結構也應該像一棵樹,葉子節點須老是由根節點衍生出來的。翻譯
要建立Context樹,第一步就是要獲得根節點, context.Background
函數的返回值就是根節點:
func Background() Context
該函數返回空的Context,該Context通常由接收請求的第一個Goroutine建立,是與進入請求對應的Context根節點,它不能被取消、沒有值、也沒有過時時間。它經常做爲處理Request的頂層context存在。
有了根節點,又該怎麼建立其它的子節點,孫節點呢?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 interface{}, val interface{}) Context
函數都接收一個 Context
類型的參數 parent
,並返回一個 Context
類型的值,這樣就層層建立出不一樣的節點。子節點是從複製父節點獲得的,而且根據接收參數設定子節點的一些狀態值,接着就能夠將子節點傳遞給下層的Goroutine了。
再回到以前的問題:該怎麼經過 Context
傳遞改變後的狀態呢?使用 Context
的Goroutine沒法取消某個操做,其實這也是符合常理的,由於這些Goroutine是被某個父Goroutine建立的,而理應只有父Goroutine能夠取消操做。在父Goroutine中能夠經過WithCancel方法得到一個cancel方法,從而得到cancel的權利。
第一個 WithCancel
函數,它是將父節點複製到子節點,而且還返回一個額外的 CancelFunc
函數類型變量,該函數類型的定義爲:
type CancelFunc func()
調用 CancelFunc
對象將撤銷對應的 Context
對象,這就是主動撤銷 Context
的方法。在父節點的 Context
所對應的環境中,經過 WithCancel
函數不只可建立子節點的 Context
,同時也得到了該節點 Context
的控制權,一旦執行該函數,則該節點 Context
就結束了,則子節點須要相似以下代碼來判斷是否已結束,並退出該Goroutine:
select { case <-cxt.Done(): // do some clean... }
WithDeadline
函數的做用也差很少,它返回的Context類型值一樣是 parent
的副本,但其過時時間由 deadline
和 parent
的過時時間共同決定。當 parent
的過時時間早於傳入的 deadline
時間時,返回的過時時間應與 parent
相同。父節點過時時,其全部的子孫節點必須同時關閉;反之,返回的父節點的過時時間則爲 deadline
。
WithTimeout
函數與 WithDeadline
相似,只不過它傳入的是從如今開始Context剩餘的生命時長。他們都一樣也都返回了所建立的子Context的控制權,一個 CancelFunc
類型的函數變量。
當頂層的Request請求函數結束後,咱們就能夠cancel掉某個context,從而層層Goroutine根據判斷 cxt.Done()
來結束。
WithValue
函數,它返回 parent
的一個副本,調用該副本的Value(key)方法將獲得val。這樣咱們不光將根節點原有的值保留了,還在子孫節點中加入了新的值,注意若存在Key相同,則會被覆蓋。
範例:
package main import ( "fmt" "time" "golang.org/x/net/context" ) func main() { // ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(time.Second*5)) ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*5) ctx = context.WithValue(ctx, "Test", "123456") // defer cancelFunc() if t, ok := ctx.Deadline(); ok { fmt.Println(time.Now()) fmt.Println(t.String()) } go func(ctx context.Context) { fmt.Println(ctx.Value("Test")) for { select { case <-ctx.Done(): fmt.Println(ctx.Err()) return // default: // continue } } }(ctx) // if ctx.Err() == nil { // fmt.Println("Sleep 10 seconds...") // time.Sleep(time.Second * 10) // } // if ctx.Err() != nil { // fmt.Println("Alredy exit...") // } time.Sleep(time.Second * 3) cancelFunc() // for { // if ctx.Err() != nil { // fmt.Println("gracefully exit...") // break // } // } }
參考:http://lanlingzi.cn/post/technical/2016/0802_go_context/?utm_source=tuicool&utm_medium=referral
http://blog.csdn.net/u014029783/article/details/53782864
http://blog.csdn.net/zdyueguanyun/article/details/64904703