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之間傳值。