- 原文地址:官方文檔 context
- 譯文地址:https://github.com/watermelo/dailyTrans
- 譯者:咔嘰咔嘰\
- 譯者水平有限呆瞻,如有翻譯或理解謬誤台夺,煩請幫忙指出
在剛剛過去的 2019 gopher china 大會上 context
概念被多次提起,包括很多框架的源碼也大量運用了痴脾〔椋看得出來 context 在
golang 的世界中是一個非常重要的知識點,所以有必要對 context
有一個基本的使用和認知赞赖。官方文檔解釋和示例都比較詳細正規(guī)滚朵,本著學習的態(tài)度翻譯一遍加深理解。
概覽
context 包定義了 Context 類型前域,它在 API
邊界和進程之間傳遞截止時間辕近,取消信號和其他請求作用域的值。
服務收到請求應該要創(chuàng)建一個 Context匿垄,對服務的響應應該要接受一個
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
的程序應遵循這些規(guī)則芯砸,以保持各個包的接口一致萧芙,并啟用靜態(tài)分析工具來檢查上下文的傳遞:
不要將 Contexts 存儲在結(jié)構(gòu)類型中;相反假丧,要將 Context
明確地傳遞給需要它的每個函數(shù)双揪。 Context 應該是第一個參數(shù),通常命名為
ctx:
func DoSomething(ctx context.Context, arg Arg) error {
// ... use ctx ...
}
即使函數(shù)允許包帚,也不要傳遞 nil Context渔期。如果你不確定要使用哪個
Context,請傳遞 context.TODO渴邦。
僅將上下文的值用于 API
邊界和進程之間的請求作用域數(shù)據(jù)疯趟,而不是將可選參數(shù)傳遞給函數(shù)。
可以將相同的 Context 傳遞給在不同 goroutine 中運行的函數(shù)谋梭;Contexts
對于同時使用多個 goroutine 是安全的信峻。
有關(guān)服務中使用 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)的資源,因此代碼應在此上下文中的操作完成后立即調(diào)用
cancel吼鱼。
示例
此示例演示了使用可取消的上下文來防止 goroutine
泄漏蓬豁。在示例函數(shù)的最后,gen 啟動的 goroutine 將返回菇肃,并且不會造成
goroutine 泄漏地粪。
package main
import (
"context"
"fmt"
)
func main() {
// gen 在單獨的 goroutine 中生成整數(shù)并將它們發(fā)送到返回的 channel。
// 一旦消費了生成的整數(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() // 當我們消費完整數(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)的資源造挽,因此代碼應在此上下文中的操作完成后立即調(diào)用
cancel碱璃。
示例
這個例子傳遞一個帶有任意截止時間的上下文來告訴一個阻塞的函數(shù)它應該在超時的時候丟棄它的任務。
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)的資源,因此代碼應在此上下文中運行的操作完成后立即調(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ù)在超時后它應該丟棄它的任務凭疮。
package main
import (
"context"
"fmt"
"time"
)
func main() {
// 傳遞一個帶超時的上下文饭耳,以告知一個阻塞的函數(shù)在超時后它應該丟棄它的任務。
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 通知一個操作丟棄它的任務执解。 CancelFunc
不等待任務停止寞肖。第一次調(diào)用后,CancelFunc 的后續(xù)調(diào)用將失效衰腌。
type CancelFunc func()
類型 Context
一個 Context 可以跨 API 邊界傳遞截止日期新蟆,取消信號和其他值。
Context 的方法可以由多個 goroutine 同時調(diào)用右蕊。
type Context interface {
// Deadline 返回完成的任務的時間琼稻,即取消此上下文的時間。
// 如果沒有設(shè)置截止時間饶囚,Deadline 返回 ok == false帕翻。
// 對截止日期的連續(xù)調(diào)用返回相同的結(jié)果。
Deadline() (deadline time.Time, ok bool)
// 當任務完成時萝风,即此上下文被取消嘀掸,Done 會返回一個關(guān)閉的channel。
// 如果此上下文一直不被取消规惰,Done 返回 nil睬塌。對 Done 的連續(xù)調(diào)用會返回相同的值。
//
// 當取消函數(shù)被調(diào)用時,WithCancel 使 Done 關(guān)閉;
// 在截止時間到期時揩晴,WithDeadline 使 Done 關(guān)閉;
// 當超時的時候勋陪,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邊界和進程之間的請求作用域數(shù)據(jù),而不是將可選參數(shù)傳遞給函數(shù)制妄。
//
// key 標識上下文中的特定值。
// 在上下文中存儲值的函數(shù)通常在全局變量中分配一個 key泵三,然后使用該 key 作為 context.WithValue 和 Context.Value 的參數(shù)耕捞。
// key 可以是支持比較的任何類型
// 包應該將 key 定義為非導出類型以避免沖突。
//
// 定義 Context key 的包應該為使用該 key 存儲的值提供類型安全的訪問:
//
// // 包使用者定義一個存儲在上下文中的 User 類型烫幕。
// package user
//
// import "context"
//
// // User 是上下文中值的類型俺抽。
// type User struct {...}
//
// // key 是此程序包中定義的 key 的非導出類型。
// // 這可以防止與其他包中定義的 key 沖突较曼。
// type key int
//
// // userKey 是上下文中 user.User 值的 key磷斧。它是不可以被導出的。
// // 客戶端使用 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伏恐。它永遠不會被取消孩哑,沒有值,也沒有截止時間翠桦。它通常由 main
函數(shù)初始化和測試使用横蜒,并作為請求的頂級 Context胳蛮。
函數(shù) TODO
func TODO() Context
TODO 返回一個非 nil 的空 Context。當不清楚使用哪個 Context
或者它還不可用時(因為周圍的函數(shù)尚未擴展為接受 Context
參數(shù))丛晌,代碼應該使用 context.TODO仅炊。
函數(shù) WithValue
func WithValue(parent Context, key, val interface{}) Context
WithValue 返回父級的副本,其中與 key 關(guān)聯(lián)的值為 val澎蛛。
僅將上下文的值用于 API
邊界和進程之間的請求作用域數(shù)據(jù)抚垄,而不是將可選參數(shù)傳遞給函數(shù)。
提供的 key
必須是可比較的谋逻,不應該是字符串類型或任何其他內(nèi)置類型呆馁,以避免使用上下文的包之間產(chǎn)生沖突。
WithValue 的使用者應該為 keys 定義他們自己的自定義類型毁兆。為了避免在分配
interface{}時指定浙滤,上下文的 keys 通常具有具體類型 struct
{}∑椋或者纺腊,導出的上下文的 key 變量的靜態(tài)類型應該是指針或接口。
示例
此示例展示如何將值傳遞給上下文以及如何檢索它(如果存在)茎芭。
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"))
}