golang context上下文信息

當(dāng)需要在多個 goroutine 中傳遞上下文信息時,可以使用 Context 實現(xiàn)。Context 除了用來傳遞上下文信息,還可以用于傳遞終結(jié)執(zhí)行子任務(wù)的相關(guān)信號,中止多個執(zhí)行子任務(wù)的 goroutine。context 中提供以下接口:
(context包下的接口)

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

Deadline 方法,返回 Context 被取消的時間,也就是完成工作的截止日期;

Done,返回一個 channel,這個 channel 會在當(dāng)前工作完成或者上下文被取消之后關(guān)閉,多次調(diào)用 Done 方法會返回同一個 channel;

Err 方法,返回 Context 結(jié)束的原因,它只會在 Done 返回的 channel 被關(guān)閉時才會返回非空的值,如果 Context 被取消,會返回 Canceled 錯誤;如果 Context 超時,會返回 DeadlineExceeded 錯誤。

Value 方法,可用于從 Context 中獲取傳遞的鍵值信息。

在 Web 請求的處理過程中,一個請求可能啟動多個 goroutine 協(xié)同工作,這些 goroutine 之間可能需要共享請求的信息,且當(dāng)請求被取消或者執(zhí)行超時時,該請求對應(yīng)的所有 goroutine 都需要快速結(jié)束,釋放資源。Context 就是為了解決上述場景而開發(fā)的,我們通過下面一個例子來演示:

package main
import (
    "context"
    "fmt"
    "time"
)
const DB_ADDRESS  = "db_address"
const CALCULATE_VALUE  = "calculate_value"
func readDB(ctx context.Context, cost time.Duration)  {
    fmt.Println("db address is", ctx.Value(DB_ADDRESS))
    select {
    case <- time.After(cost): //  模擬數(shù)據(jù)庫讀取
        fmt.Println("read data from db")
    case <-ctx.Done():
        fmt.Println(ctx.Err()) // 任務(wù)取消的原因
        // 一些清理工作
    }
}
func calculate(ctx context.Context, cost time.Duration)  {
    fmt.Println("calculate value is", ctx.Value(CALCULATE_VALUE))
    select {
    case <- time.After(cost): //  模擬數(shù)據(jù)計算
        fmt.Println("calculate finish")
    case <-ctx.Done():
        fmt.Println(ctx.Err()) // 任務(wù)取消的原因
        // 一些清理工作   比如關(guān)閉goroutine
    }
}
func main()  {
    ctx := context.Background(); // 創(chuàng)建一個空的上下文
    // 添加上下文信息
    ctx = context.WithValue(ctx, DB_ADDRESS, "localhost:10086")
    ctx = context.WithValue(ctx, CALCULATE_VALUE, 1234)
    // 設(shè)定子 Context 2s 后執(zhí)行超時返回
    ctx, cancel := context.WithTimeout(ctx, time.Second*2)   //會返回一個取消函數(shù)
    defer cancel()
    // 設(shè)定執(zhí)行時間為 4 s
    go readDB(ctx, time.Second*4)
    go calculate(ctx, time.Second*4)

    // 充分執(zhí)行
    time.Sleep(time.Second * 5)
}

用context作為協(xié)成的參數(shù)

在上述例子中,我們模擬了一個請求中同時進(jìn)行數(shù)據(jù)庫訪問和邏輯計算的操作,在請求執(zhí)行超時時,及時關(guān)閉尚未執(zhí)行結(jié)束 goroutine。我們首先通過 context.WithValue 方法為 context 添加上下文信息,Context 在多個 goroutine 中是并發(fā)安全的,可以安全地在多個 goroutine 中對 Context 中的上下文數(shù)據(jù)進(jìn)行讀取。接著使用 context.WithTimeout 方法設(shè)定了 Context 的超時時間為 2s,并傳遞給 readDB 和 calculate 兩個 goroutine 執(zhí)行子任務(wù)。在 readDB 和 calculate 方法中,使用 select 語句對 Context 的 Done 通道進(jìn)行監(jiān)控。由于我們設(shè)定了子 Context 將在 2s 之后超時,所以它將在 2s 之后關(guān)閉 Done 通道;然而預(yù)設(shè)的子任務(wù)執(zhí)行時間為 4s,對應(yīng)的 case 語句尚未返回,執(zhí)行被取消,進(jìn)入到清理工作的 case 語句中,結(jié)束掉當(dāng)前的 goroutine 所執(zhí)行的任務(wù)。預(yù)期的輸出結(jié)果如下:


這就是超時報錯
如果你想看看取消不錯,可以修改主goroutine的slesp為1秒,然后再defer語句上添加defer fmt.Println(ctx.Err())

使用 Context,能夠有效地在一組 goroutine 中傳遞共享值、取消信號、deadline 等信息,及時關(guān)閉不需要的 goroutine。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容