Golang context包解讀

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

context使用

不管是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

相關文章
相關標籤/搜索