Golang context包解讀


Context 通常被譯作 上下文 ,一般理解為程序單元的一個運行狀態、現場、快照,而翻譯中 上下 又很好地詮釋了其本質,上下上下則是存在上下層的傳遞,  會把內容傳遞給  。

在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是否已關閉的信號。

  • 當 Done 信道關閉后, Err 方法表明 Contex t被撤的原因。

  • Value 可以讓Goroutine共享一些數據,當然獲得數據是協程安全的。但使用這些數據的時候要注意同步,比如返回了一個map,而這個map的讀寫則要加鎖。

Context 接口沒有提供方法來設置其值和過期時間,也沒有提供方法直接將其自身撤銷。也就是說, Context 不能改變和撤銷其自身。那么該怎么通過 Context 傳遞改變后的狀態呢?

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM