context
context包定義了上下文類型,該類型在API邊界之間以及進程之間傳遞截止日期,取消信號和其他請求范圍的值。
對服務器的傳入請求應創建一個Context,而對服務器的傳出調用應接受一個Context。它們之間的函數調用鏈必須傳播Context,可以選擇將其替換為使用WithCancel,WithDeadline,WithTimeout或WithValue創建的派生Context。取消context后,從該Context派生的所有Context也會被取消。
WithCancel,WithDeadline和WithTimeout函數采用Context(父級)并返回派生的Context(子級)和CancelFunc。調用CancelFunc會取消該子代及其子代,刪除父代對該子代的引用,并停止所有關聯的計時器。未能調用CancelFunc會使子代及其子代泄漏,直到父代被取消或計時器觸發。審核工具檢查所有控制流路徑上是否都使用了CancelFuncs。
使用Context的程序應遵循以下規則,以使各個包之間的接口保持一致,并啟用靜態分析工具來檢查上下文傳播:
- 不要將Context存儲在結構類型中;
- 將Context明確傳遞給需要它的每個函數;
- Context應該是第一個參數,通常命名為ctx:
func DoSomething(ctx context.Context, arg Arg) error { // ... use ctx ... }
即使函數允許,也不要傳遞nil Context。如果不確定使用哪個上下文,請傳遞context.TODO。
僅將Context值用于傳遞流程和API的請求范圍的數據,而不用于將可選參數傳遞給函數。
可以將相同的Context傳遞給在不同goroutine中運行的函數。Context對于由多個goroutine同時使用是安全的。
Variables
var Canceled = errors.New("context canceled")
var DeadlineExceeded error = deadlineExceededError{}
Canceled是Context.Err類型,取消Context時返回的錯誤。
DeadlineExceeded是上下文的最后期限過去時Context.Err返回的錯誤。
type CancelFunc
type CancelFunc func()
CancelFunc告訴操作停止工作。 CancelFunc不等待工作停止。 在第一個調用之后,隨后對CancelFunc的調用什么都不做。
type Context
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
其中
Deadline
方法需要返回當前Context
被取消的時間,也就是完成工作的截止時間(deadline);Done
方法需要返回一個Channel
,這個Channel
會在當前工作完成或者上下文被取消之后關閉,多次調用Done
方法會返回同一個Channel
;Err
方法會返回當前Context
結束的原因,它只會在Done
返回的Channel
被關閉時才會返回非空的值;
- 如果當前
Context
被取消就會返回Canceled
錯誤;- 如果當前
Context
超時就會返回DeadlineExceeded
錯誤;Value
方法會從Context
中返回鍵對應的值,對于同一個上下文來說,多次調用Value
并傳入相同的Key
會返回相同的結果,該方法僅用于傳遞跨API和進程間跟請求域的數據;
func Background
func Background() Context
Background返回一個非空的Context。 它永遠不會被取消,沒有值,也沒有期限。 它通常由主要功能,初始化和測試使用,并用作傳入請求的頂級Context。
func TODO
func TODO() Context
TODO返回一個非空的Context。 當不清楚要使用哪個上下文或尚不可用時(因為尚未擴展周圍的功能以接受Context參數),代碼應使用context.TODO。 靜態分析工具可識別TODO,該工具可確定上下文是否在程序中正確傳播。
func WithCancel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
WithCancel返回具有新的“完成”通道的父級副本。 當調用返回的cancel函數或關閉父上下文的Done通道時(以先發生的為準),關閉返回的上下文的Done通道。
取消此上下文將釋放與其關聯的資源,因此在此上下文中運行的操作完成后,代碼應立即調用cancel。
func main() { gen := func(ctx context.Context) <-chan int { dst := make(chan int) n := 1 go func() { for { select { case <-ctx.Done(): return // returning not to leak the goroutine case dst <- n: n++ } } }() return dst } ctx, cancel := context.WithCancel(context.Background()) defer cancel() // cancel when we are finished consuming integers for n := range gen(ctx) { fmt.Println(n) if n == 5 { break } } }
上面的示例代碼中,gen函數在單獨的goroutine中生成整數并將它們發送到返回的通道。 gen的調用者在使用生成的整數之后需要取消上下文,以免gen啟動的內部goroutine發生泄漏。
func WithDeadline
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
WithDeadline返回父上下文的副本,并將截止日期調整為不遲于d。 如果父母的截止日期早于d,則WithDeadline(parent,d)在語義上等同于父母。 當截止日期到期,調用返回的cancel函數或關閉父上下文的Done通道(以先到者為準)時,將關閉返回的上下文的Done通道。
取消此上下文將釋放與其關聯的資源,因此在此上下文中運行的操作完成后,代碼應立即調用cancel。
func main() {
d := time.Now().Add(50 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(), d)
// Even though ctx will be expired, it is good practice to call its
// cancelation function in any case. Failure to do so may keep the
// context and its parent alive longer than necessary.
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err())
}
}
上面的代碼中,定義了一個50毫秒之后過期的deadline,然后我們調用context.WithDeadline(context.Background(), d)得到一個上下文(ctx)和一個取消函數(cancel),然后使用一個select讓主程序陷入等待:等待1秒后打印overslept退出或者等待ctx過期后退出。 因為ctx50毫秒后就過期,所以ctx.Done()會先接收到值,上面的代碼會打印ctx.Err()取消原因。
func WithTimeout
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
WithTimeout返回WithDeadline(parent, time.Now().Add(timeout))方法。
取消此Context 將釋放與之關聯的資源,因此在此Context 中運行的操作完成后,代碼應立即調用cancel:
func main() {
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()) // prints "context deadline exceeded"
}
}
func WithValue
func WithValue(parent Context, key, val interface{}) Context
WithValue返回父項的副本,其中與key關聯的值為val。
僅將Context值用于傳遞流程和API的請求范圍的數據,而不用于將可選參數傳遞給函數。
提供的密鑰必須具有可比性,并且不能為字符串類型或任何其他內置類型,以避免使用上下文在程序包之間發生沖突。 WithValue的用戶應定義自己的密鑰類型。 為了避免在分配給接口{}時進行分配,上下文鍵通常具有具體的類型struct {}。 另外,導出的上下文鍵變量的靜態類型應該是指針或接口。
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"))
}