- 原文地址:官方文檔 context
- 譯文地址:https://github.com/watermelo/dailyTrans
- 譯者:咔嘰咔嘰\
- 譯者水平有限,如有翻譯或理解謬誤,煩請幫忙指出
在剛剛過去的 2019 gopher china 大會上 context
概念被多次提起,包括很多框架的源碼也大量運(yùn)用了。看得出來 context 在
golang 的世界中是一個非常重要的知識點,所以有必要對 context
有一個基本的使用和認(rèn)知。官方文檔解釋和示例都比較詳細(xì)正規(guī),本著學(xué)習(xí)的態(tài)度翻譯一遍加深理解。
概覽
context 包定義了 Context 類型,它在 API
邊界和進(jìn)程之間傳遞截止時間,取消信號和其他請求作用域的值。
服務(wù)收到請求應(yīng)該要創(chuàng)建一個 Context,對服務(wù)的響應(yīng)應(yīng)該要接受一個
Context。它們之間的函數(shù)調(diào)用鏈必須傳遞 Context,也可以使用
WithCancel,WithDeadline,WithTimeout 或 WithValue 等方法創(chuàng)建派生
Context 替換它。取消 Context 后,也會取消從中派生的所有 Context。
WithCancel,WithDeadline 和 WithTimeout 函數(shù)接受
Context(父)并返回派生的 Context(子)和一個 CancelFunc 函數(shù)。調(diào)用
CancelFunc 函數(shù)會取消該派生的子 Context 及其孫子
Context,刪除父項對子項的引用,并停止任何關(guān)聯(lián)的計時器。如果沒有調(diào)用
CancelFunc 會泄漏子項和孫子項,直到父項被取消或計時器觸發(fā)。 go vet
工具檢查是否在所有控制流路徑上使用了 CancelFuncs。
使用 Contexts
的程序應(yīng)遵循這些規(guī)則,以保持各個包的接口一致,并啟用靜態(tài)分析工具來檢查上下文的傳遞:
不要將 Contexts 存儲在結(jié)構(gòu)類型中;相反,要將 Context
明確地傳遞給需要它的每個函數(shù)。 Context 應(yīng)該是第一個參數(shù),通常命名為
ctx:
func DoSomething(ctx context.Context, arg Arg) error {
// ... use ctx ...
}
即使函數(shù)允許,也不要傳遞 nil Context。如果你不確定要使用哪個
Context,請傳遞 context.TODO。
僅將上下文的值用于 API
邊界和進(jìn)程之間的請求作用域數(shù)據(jù),而不是將可選參數(shù)傳遞給函數(shù)。
可以將相同的 Context 傳遞給在不同 goroutine 中運(yùn)行的函數(shù);Contexts
對于同時使用多個 goroutine 是安全的。
有關(guān)服務(wù)中使用 Contexts
的示例代碼,請參考https://blog.golang.org/context 。
變量
Canceled 是上下文取消時,通過 Context.Err 返回的錯誤。
var Canceled = errors.New("context canceled")
DeadlineExceeded 是在上下文超過截止時間時,通過 Context.Err 返回的錯誤。
var DeadlineExceeded error = deadlineExceededError{}
函數(shù) WithCancel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
WithCancel 返回帶有新 Done channel 的父副本。返回的上下文的 Done channel
在調(diào)用返回的取消函數(shù)或父上下文的 Done channel
關(guān)閉時關(guān)閉,取決于誰先發(fā)生。
取消此上下文會釋放與其相關(guān)的資源,因此代碼應(yīng)在此上下文中的操作完成后立即調(diào)用
cancel。
示例
此示例演示了使用可取消的上下文來防止 goroutine
泄漏。在示例函數(shù)的最后,gen 啟動的 goroutine 將返回,并且不會造成
goroutine 泄漏。
package main
import (
"context"
"fmt"
)
func main() {
// gen 在單獨(dú)的 goroutine 中生成整數(shù)并將它們發(fā)送到返回的 channel。
// 一旦消費(fèi)了生成的整數(shù),gen 的調(diào)用者需要取消上下文,從而不會泄漏 gen 啟動的內(nèi)部 goroutine。
gen := func(ctx context.Context) <-chan int {
dst := make(chan int)
n := 1
go func() {
for {
select {
case <-ctx.Done():
return // 返回以致不泄露 goroutine
case dst <- n:
n++
}
}
}()
return dst
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 當(dāng)我們消費(fèi)完整數(shù)后調(diào)用取消函數(shù)
for n := range gen(ctx) {
fmt.Println(n)
if n == 5 {
break
}
}
}
函數(shù) WithDeadline
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
WithDeadline 返回父上下文的副本,其截止日期調(diào)整為不遲于
d。如果父級的截止日期早于 d,則 WithDeadline(parent, d)在語義上等同于
parent。返回的上下文的 Done channel
在超過截止時間后,調(diào)用返回的取消函數(shù)時或父上下文的 Done channel
關(guān)閉時關(guān)閉,三者取決于誰先發(fā)生。
取消此上下文會釋放與其關(guān)聯(lián)的資源,因此代碼應(yīng)在此上下文中的操作完成后立即調(diào)用
cancel。
示例
這個例子傳遞一個帶有任意截止時間的上下文來告訴一個阻塞的函數(shù)它應(yīng)該在超時的時候丟棄它的任務(wù)。
package main
import (
"context"
"fmt"
"time"
)
func main() {
d := time.Now().Add(50 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(), d)
// 即使 ctx 將要過期,在任何情況下最好也要調(diào)用它的取消函數(shù)。
// 如果不這樣做,可能會使上下文及其父級的活動時間超過必要時間。
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err())
}
}
函數(shù) WithTimeout
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
WithTimeout 返回 WithDeadline(parent, time.Now().Add(timeout))。
取消此上下文會釋放與其關(guān)聯(lián)的資源,因此代碼應(yīng)在此上下文中運(yùn)行的操作完成后立即調(diào)用
cancel:
func slowOperationWithTimeout(ctx context.Context) (Result, error) {
ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
defer cancel() // 如果 slowOperation 在超時之前完成,則釋放資源
completes before timeout elapses
return slowOperation(ctx)
}
示例
此示例傳遞具有超時的上下文,以告知一個阻塞的函數(shù)在超時后它應(yīng)該丟棄它的任務(wù)。
package main
import (
"context"
"fmt"
"time"
)
func main() {
// 傳遞一個帶超時的上下文,以告知一個阻塞的函數(shù)在超時后它應(yīng)該丟棄它的任務(wù)。
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err()) //打印 "context deadline exceeded"
}
}
類型 CancelFunc
CancelFunc 通知一個操作丟棄它的任務(wù)。 CancelFunc
不等待任務(wù)停止。第一次調(diào)用后,CancelFunc 的后續(xù)調(diào)用將失效。
type CancelFunc func()
類型 Context
一個 Context 可以跨 API 邊界傳遞截止日期,取消信號和其他值。
Context 的方法可以由多個 goroutine 同時調(diào)用。
type Context interface {
// Deadline 返回完成的任務(wù)的時間,即取消此上下文的時間。
// 如果沒有設(shè)置截止時間,Deadline 返回 ok == false。
// 對截止日期的連續(xù)調(diào)用返回相同的結(jié)果。
Deadline() (deadline time.Time, ok bool)
// 當(dāng)任務(wù)完成時,即此上下文被取消,Done 會返回一個關(guān)閉的channel。
// 如果此上下文一直不被取消,Done 返回 nil。對 Done 的連續(xù)調(diào)用會返回相同的值。
//
// 當(dāng)取消函數(shù)被調(diào)用時,WithCancel 使 Done 關(guān)閉;
// 在截止時間到期時,WithDeadline 使 Done 關(guān)閉;
// 當(dāng)超時的時候,WithTimeout使 Done 關(guān)閉。
//
// Done 可以使用 select 語句:
//
// // Stream 使用 DoSomething 生成值并將它們發(fā)送到 out,
// // 直到 DoSomething 返回錯誤或 ctx.Done 關(guān)閉。
// func Stream(ctx context.Context, out chan<- Value) error {
// for {
// v, err := DoSomething(ctx)
// if err != nil {
// return err
// }
// select {
// case <-ctx.Done():
// return ctx.Err()
// case out <- v:
// }
// }
// }
//
// 查看 https://blog.golang.org/pipelines 獲得更多關(guān)于怎么使用 Done channel 去取消的例子
Done() <-chan struct{}
// 如果 Done 尚未關(guān)閉,則 Err 返回 nil。
// 如果 Done 關(guān)閉,Err 會返回一個非nil的錯誤,原因:
// 如果上下文被取消,則調(diào)用 Canceled;
// 如果上下文的截止時間已過,則調(diào)用 DeadlineExceeded。
// 在 Err 返回非 nil 錯誤后,對 Err 的連續(xù)調(diào)用返回相同的錯誤。
Err() error
// Value 返回與此上下文關(guān)聯(lián)的 key 的值,如果沒有值與 key 關(guān)聯(lián),則返回nil。使用相同的 key 連續(xù)調(diào)用 Value 會返回相同的結(jié)果。
//
// 僅將上下文的值用于API邊界和進(jìn)程之間的請求作用域數(shù)據(jù),而不是將可選參數(shù)傳遞給函數(shù)。
//
// key 標(biāo)識上下文中的特定值。
// 在上下文中存儲值的函數(shù)通常在全局變量中分配一個 key,然后使用該 key 作為 context.WithValue 和 Context.Value 的參數(shù)。
// key 可以是支持比較的任何類型
// 包應(yīng)該將 key 定義為非導(dǎo)出類型以避免沖突。
//
// 定義 Context key 的包應(yīng)該為使用該 key 存儲的值提供類型安全的訪問:
//
// // 包使用者定義一個存儲在上下文中的 User 類型。
// package user
//
// import "context"
//
// // User 是上下文中值的類型。
// type User struct {...}
//
// // key 是此程序包中定義的 key 的非導(dǎo)出類型。
// // 這可以防止與其他包中定義的 key 沖突。
// type key int
//
// // userKey 是上下文中 user.User 值的 key。它是不可以被導(dǎo)出的。
// // 客戶端使用 user.NewContext 和 user.FromContext 而不是直接使用 key。
// var userKey key
//
// // NewContext 返回一個帶有值為 u 的新的上下文。
// func NewContext(ctx context.Context, u *User) context.Context {
// return context.WithValue(ctx, userKey, u)
// }
//
// // FromContext 返回存儲在 ctx 中的 User 值(如果有的話)。
// func FromContext(ctx context.Context) (*User, bool) {
// u, ok := ctx.Value(userKey).(*User)
// return u, ok
// }
Value(key interface{}) interface{}
}
函數(shù) Background
func Background() Context
Background 返回一個非 nil 的空
Context。它永遠(yuǎn)不會被取消,沒有值,也沒有截止時間。它通常由 main
函數(shù)初始化和測試使用,并作為請求的頂級 Context。
函數(shù) TODO
func TODO() Context
TODO 返回一個非 nil 的空 Context。當(dāng)不清楚使用哪個 Context
或者它還不可用時(因為周圍的函數(shù)尚未擴(kuò)展為接受 Context
參數(shù)),代碼應(yīng)該使用 context.TODO。
函數(shù) WithValue
func WithValue(parent Context, key, val interface{}) Context
WithValue 返回父級的副本,其中與 key 關(guān)聯(lián)的值為 val。
僅將上下文的值用于 API
邊界和進(jìn)程之間的請求作用域數(shù)據(jù),而不是將可選參數(shù)傳遞給函數(shù)。
提供的 key
必須是可比較的,不應(yīng)該是字符串類型或任何其他內(nèi)置類型,以避免使用上下文的包之間產(chǎn)生沖突。
WithValue 的使用者應(yīng)該為 keys 定義他們自己的自定義類型。為了避免在分配
interface{}時指定,上下文的 keys 通常具有具體類型 struct
{}。或者,導(dǎo)出的上下文的 key 變量的靜態(tài)類型應(yīng)該是指針或接口。
示例
此示例展示如何將值傳遞給上下文以及如何檢索它(如果存在)。
package main
import (
"context"
"fmt"
)
func main() {
type favContextKey string
f := func(ctx context.Context, k favContextKey) {
if v := ctx.Value(k); v != nil {
fmt.Println("found value:", v)
return
}
fmt.Println("key not found:", k)
}
k := favContextKey("language")
ctx := context.WithValue(context.Background(), k, "Go")
f(ctx, k)
f(ctx, favContextKey("color"))
}