golang開發 深入理解 context

context的歷史

context包在Go 1.7版本正式加入Go標準庫。在加入之前我們看看Go團隊核心成員Sameer Ajmani在2014年發表的一篇關於context介紹博客,地址:https://go.dev/blog/context 下面是介紹的翻譯。

在Go服務器中,每個傳入的請求都在自己的goroutine中處理。請求處理程序經常啓動額外的goroutine來訪問後端,如數據庫和RPC服務。處理請求的goroutine集合通常需要訪問特定於請求的值,例如最終用戶的身份、授權令牌和請求的截止日期。當一個請求被取消或超時時,所有處理該請求的goroutine都應該迅速退出,這樣系統就可以回收它們正在使用的任何資源。

在Google,我們開發了一個 context 包,可以輕鬆地將請求範圍內的值、取消信號和截止日期跨API邊界傳遞給處理請求所涉及的所有goroutine。該軟件包作爲上下文公開提供。

可以看到介紹裏面主要說的是,應用中怎麼控制大量的goroutine退出釋放資源、請求範圍內怎麼傳值,也就是說,context的引入主要是爲了解決這兩個問題。

context的基本使用

context初始化的方法有五個,一個是main goroutine 初始化,一個主要是是用來傳值的,經常使用的就三個。
在這裏插入圖片描述
未超時的時候,執行取消,依賴取消函數

func main() {
    ctx := context.Background()
    ctx = context.WithValue(ctx, "main", "main-value")

    ctx1,cancel := context.WithDeadline(ctx, time.Now().Add(3*time.Second))
    go cancelFunc(ctx1)
    //取消函數
    cancel()
    time.Sleep(time.Duration(5*time.Second))
}

執行結果
main-param main-value
退出協程

超時的時候執行結果,超過三秒的超時時間,依賴超時時間

func main() {
    ctx := context.Background()
    ctx = context.WithValue(ctx, "main", "main-value")

    ctx1,_ := context.WithDeadline(ctx, time.Now().Add(3*time.Second))
    go cancelFunc(ctx1)
    time.Sleep(time.Duration(5*time.Second))
}

執行結果
main-param main-value
1秒
1秒
1秒
退出協程

看註釋和執行流程,基本能理解,main函數調用cancel就能取消正在執行的goroutine協程。WithDeadline和WithTimeout 除過可以通過調用取消函數,還可以通過設置超時時間點和時間段取消goroutine的執行。

context如何控制多個goroutine,如何傳值給多個goroutine

在這裏插入圖片描述
看圖示,root context控制着所有的goroutine,context1控制着 goroutine2 goroutine3 goroutine4 goroutine5 goroutine6 goroutine7,context2 控制着goroutine4 goroutine5,context3 控制着goroutine6 goroutine7。

使用圖示的goroutine 編寫測試代碼

package main

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

func func1(ctx context.Context) {
    fmt.Println("func1-main-param", ctx.Value("main"))
    ctx1 := context.WithValue(ctx, "func1-param", "func1-value")
    go func2(ctx1)
    select {
    case <-ctx1.Done():
        fmt.Println("goroutine 1 exit")
    }
}

func func2(ctx context.Context) {
    fmt.Println("func2-main-param", ctx.Value("main"))
    fmt.Println("func2-func1-param", ctx.Value("func1-param"))
    ctx2, _ := context.WithCancel(ctx)
    go func4(ctx2)
    go func5(ctx2)
    select {
    case <-ctx2.Done():
        fmt.Println("goroutine 2 exit")
    }
}

func func4(ctx context.Context) {
    select {
    case <-ctx.Done():
        fmt.Println("goroutine 4 exit")
    }
}

func func5(ctx context.Context) {
    select {
    case <-ctx.Done():
        fmt.Println("goroutine 5 exit")
    }
}

func main() {
    ctx := context.Background()
    ctx = context.WithValue(ctx, "main", "main-value")
    ctx,cancel := context.WithCancel(ctx)
    go func1(ctx)
    cancel()
    time.Sleep(time.Duration(5*time.Second))
}

運行結果

func1-main-param main-value
goroutine 1 exit
func2-main-param main-value
func2-func1-param func1-value
goroutine 2 exit
goroutine 5 exit
goroutine 4 exit

可以看到最終實現結果和場景,跟Sameer Ajmani在博客描述的是一樣的,context的出現主要兩個作用,控制 goroutine,goroutine之間傳值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章