Go Context包使用

前言

做爲go的使用者,大家應該都多多少少的見過Context包。可是因爲我得懶惰都沒有跳轉進去好好看看,導致我對Context包理解並不深。寫博客是一個很好的方式提醒自己不要懶惰,而且還能當成自己的筆記,平時翻一翻加深下記憶。

Context使用場景

目前總結兩種使用場景:
1. 主動停止groutine
2. 傳遞數據

Context是什麼?

Context直譯爲上下文,我們來看一下Context包中對它的說明,在go1.7之後已經添加到了標準庫中,我們之間可以在/src/context中查看。

Context包中對Context的說明

英文水平不是太好就直譯了過來,最近有在學習英文,遇到了很多優秀的人,真的是比你優秀的人比你還努力。又扯遠了…

  1. 包context定義了Context類型,幷包含deadlines(結束時間),cancelation signals(取消信號),和其他的請求api範圍的值。
  2. 對服務器的請求應該創建一個Context,服務器發出的外向請求應該接受Context。鏈式的函數調用之間必須傳遞Context,隨意的更換它使用一個導出Context使用WithCancel、WithDeadline、WithTimeout、WithValue。當一個Context取消所有從它導出的Contexts都會被取消。
  3. WithCancel、WithDeadline、WithTimeout函數攜帶一個Context(父)並返回一個導出的Context(子)和一個CancelFunc(取消函數)。調用CancelFunc函數來取消子和他的孩子,移除父母的參考孩子,並停止任何關聯的定時器。調用CancelFunc失敗會泄露子和他的孩子直到父取消或者計時器超時。go的審查工具被用在所有的control-flow路徑下檢查CancelFuncs。

程序在使用Context應遵循如下規則,以保證接口的一致,並允許靜態分析工具檢查Context傳遞。
1. 不要將 Contexts 放入結構體,相反context應該作爲第一個參數傳入,命名爲ctx。

func DoSomething(ctx context.Context, arg Arg) error {
         ... use ctx ...
}

2. 即使函數允許也不要傳遞一個nil的Context。如果不確定使用哪種Conetex,傳遞context.TODO
3. 使用context的Value相關方法只應該用於在程序和接口中傳遞的和請求相關的數據,不要用它來傳遞一些可選的參數。
4. 相同的Context可以在不同的goroutines中傳遞,Contexts是線程安全的。

Context包的核心

//  context 包裏的方法是線程安全的,可以被多個 goroutine 使用    
type Context interface {  
    // 如果存在,Deadline 返回Context將要關閉的時間  
    Deadline() (deadline time.Time, ok bool)

    // 當Context 被 canceled 或是 times out 的時候,Done 返回一個被 closed 的channel      
    Done() <-chan struct{}        

    // 在 Done 的 channel被closed 後, Err 代表被關閉的原因   
    Err() error 

    // 如果存在,Value 返回與 key 相關了的值,不存在返回 nil  
    Value(key interface{}) interface{}
}

Context包中的導出方法

  1. Background()
    該函數返回空的Context,該Context一般由接收請求的第一個Goroutine創建,是與進入請求對應的Context根節點,它不能被取消、沒有值、也沒有過期時間。它常常作爲處理Request的頂層context存在。
  2. TODO()
    該函數返回空的Context,可以使用context.TODO,當不清楚使用哪個Context,或者不確定哪個可使用。
  3. CancelFunc()
    該函數通知放棄操作者的工作,並不會等待工作停止。第一次調用之後,在調用不會做任何操作。
  4. WithCancel(parent Context)
    該函數返回一個 cancelCtx ,同時返回一個 CancelFunc,CancelFunc 是 context 包中定義的一個函數類型:type CancelFunc func()。調用這個 CancelFunc 時,關閉對應的c.done,也就是讓他的後代goroutine退出。
  5. WithDeadline(parent Context, deadline time.Time)
    該函數返回的Context類型值同樣是parent的副本,但其過期時間由deadline和parent的過期時間共同決定。當parent的過期時間早於傳入的deadline時間時,返回的過期時間應與parent相同。父節點過期時,其所有的子孫節點必須同時關閉;反之,返回的父節點的過期時間則爲deadline。
  6. WithTimeout(parent Context, timeout time.Duration)
    WithTimeout函數與WithDeadline類似,只不過它傳入的是從現在開始Context剩餘的生命時長。他們都同樣也都返回了所創建的子Context的控制權,一個CancelFunc類型的函數變量。
  7. WithValue(parent Context, key, val interface{})
    返回parent的一個副本,調用該副本的Value(key)方法將得到val。這樣我們不光將根節點原有的值保留了,還在子孫節點中加入了新的值,注意若存在Key相同,則會被覆蓋。

示例1 主動停止gorutine

import (
    "context"
    "fmt"
    "time"
)

func run(ctx context.Context,threadId int){
    for {
        select {
        case <-ctx.Done():
            fmt.Println("timeout")
        default:
            fmt.Println("thred runing ", threadId)
        }
    }
}

func timeout(cancel context.CancelFunc){
    time.Sleep( 1 * time.Second)
    //取消
    cancel()
}

func main() {
    //使用Backgroud()
    ctx, cancel := context.WithCancel(context.Background())
    go run(ctx,1)
    go run(ctx,2)
    go run(ctx,3)
    go timeout(cancel)
    time.Sleep(2 * time.Second)
}

執行以上程序,會看到不斷的在打印threadId直到timeout調用了cancel主動停止,後開始不斷打印timeout直至主程序退出。示例主要展示了WithCancel,Background的用法。

在上面的例子中我們自己手寫了一個timeout,context包中已經給我們提供好了,下面我們就改造下。
示例2 使用WithDeadline,WithTimeout

import (
    "context"
    "fmt"
    "time"
)

func run(ctx context.Context,threadId int){
    for {
        select {
        case <-ctx.Done():
            fmt.Println("timeout")
        default:
            fmt.Println("thred runing ", threadId)
        }
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(),1 * time.Second)
    defer cancel()
    go run(ctx,1)
    go run(ctx,2)
    go run(ctx,3)
    time.Sleep(2 * time.Second)
}

由於WithTimeout與WithDeadline功能類似就不在舉例了,運行以上示例與示例1輸出同樣的結果。

示例3 傳遞數據

import (
    "context"
    "fmt"
    "time"
)
var key string = "Hello word!"

func run(ctx context.Context){
    for {
        select {
        case <-ctx.Done():
            fmt.Println("timeout")
        default:
            fmt.Println(ctx.Value(key))
        }
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(),1 * time.Second)
    value := context.WithValue(ctx,key,"This is my test")
    defer cancel()
    go run(value)
    time.Sleep(2 * time.Second)
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章