Go 編程實踐 - Go Context
1、context 包的引入
context 包是在 Go 1.7 引入的,它爲在多個 goroutine 之間傳遞請求範圍的上下文信息和控制 goroutine 的生命週期提供了一種標準的方式。
2、context 包的主要作用
**傳遞上下文信息:**context 可用於在 goroutine 之間傳遞請求範圍的數據,如請求的認證信息、請求的截止時間、請求的 ID 等。這些信息在 goroutine 之間傳遞時可以保持一致性。
**控制 goroutine 生命週期:**可以使用 context 來通知 goroutine 取消操作,避免資源浪費和不必要的計算,尤其在請求超時、請求失敗的情況下。
**管理超時和截止時間:**設置超時或截止時間,當達到超時或截止時間時,goroutine 可以提前結束,防止長時間運行的 goroutine 導致資源耗盡或性能問題。
3、基本使用方法
3.1、創建 Context
- **context.Background():**通常作爲根 Context 使用,是一個空的 Context,作爲所有 Context 的基礎。
ctx := context.Background()
- **context.WithCancel(ctx):**創建一個可取消的 Context,並返回一個新的 Context 和一個取消函數。
ctx, cancel := context.WithCancel(ctx)
- **context.WithTimeout(ctx, timeout):**創建一個帶有超時的 Context,當超過 timeout 時,Context 會自動取消。
ctx, cancel := context.WithTimeout(ctx, time.Second*5)
- **context.WithDeadline(ctx, deadline):**創建一個在 deadline 時間到達時自動取消的 Context。
deadline := time.Now().Add(time.Second * 5)
ctx, cancel := context.WithDeadline(ctx, deadline)
- **context.WithValue(ctx, key, val):**創建一個攜帶鍵值對的 Context,可以用來存儲請求範圍的數據。
ctx = context.WithValue(ctx, "requestID", "123456")
3.2、檢查 Context 狀態
- **ctx.Done():**返回一個 <-chan struct{},當 Context 被取消或超時,該通道會關閉。
select {
case <-ctx.Done():
fmt.Println("Context done:", ctx.Err())
default:
// 正常操作
}
-
**ctx.Err():**返回 Context 被取消的原因,可能是 context.Canceled 或 context.DeadlineExceeded。
-
**ctx.Value(key):**獲取存儲在 Context 中的值。
requestID := ctx.Value("requestID").(string)
4、經典使用場景
4.1、設置取消
一個請求觸發多個 goroutine 執行任務,當其中一個任務完成或失敗時,需要取消其它 goroutine 的操作。
package main
import (
"context"
"fmt"
"time"
)
func main() {
// 創建一個可取消的 Context
ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("Goroutine 1: Operation canceled")
return
default:
fmt.Println("Goroutine 1: Working...")
time.Sleep(time.Second * 1)
}
}
}(ctx)
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("Goroutine 2: Operation canceled")
return
default:
fmt.Println("Goroutine 2: Working...")
time.Sleep(time.Second * 2)
}
}
}(ctx)
// 模擬操作完成或失敗,調用取消函數
time.Sleep(time.Second * 3)
cancel()
// 等待一段時間,觀察輸出
time.Sleep(time.Second * 2)
}
4.2、設置超時
Go 的 database/sql 標準庫中的 DB.QueryContext、DB.ExecContext 等方法都可接受 context.Context 參數,能在數據庫操作時傳遞上下文。
package main
import (
"context"
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// 連接數據庫
dsn := "user:password@tcp(localhost:3306)/dbname?parseTime=True&loc=Local&charset=utf8mb4"
db, err := sql.Open("mysql", dsn)
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 設置 3 秒超時
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
// 執行查詢
//db.Query()
rows, err := db.QueryContext(ctx, "select id, name from tb1 limit 3")
//rows, err := db.QueryContext(ctx, "select sleep(5) as id, 'abc'") // context deadline exceeded
if err != nil {
log.Fatal(err)
}
defer rows.Close()
// 處理查詢
for rows.Next() {
var id int
var name string
if err := rows.Scan(&id, &name); err != nil {
log.Fatal(err)
}
fmt.Printf("ID: %d, Name: %s\n", id, name)
}
}
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/m4Kyj0g2ulH53EjTRrrfsQ