[譯] golang 官方文檔 context

在剛剛過去的 2019 gopher china 大會上 context 概念被屢次提起,包括不少框架的源碼也大量運用了。看得出來 context 在 golang 的世界中是一個很是重要的知識點,因此有必要對 context 有一個基本的使用和認知。官方文檔解釋和示例都比較詳細正規,本着學習的態度翻譯一遍加深理解。git

概覽

context 包定義了 Context 類型,它在 API 邊界和進程之間傳遞截止時間,取消信號和其餘請求做用域的值。github

服務收到請求應該要建立一個 Context,對服務的響應應該要接受一個 Context。它們之間的函數調用鏈必須傳遞 Context,也可使用 WithCancel,WithDeadline,WithTimeout 或 WithValue 等方法建立派生 Context 替換它。取消 Context 後,也會取消從中派生的全部 Context。golang

WithCancel,WithDeadline 和 WithTimeout 函數接受 Context(父)並返回派生的 Context(子)和一個 CancelFunc 函數。調用 CancelFunc 函數會取消該派生的子 Context 及其孫子 Context,刪除父項對子項的引用,並中止任何關聯的計時器。若是沒有調用 CancelFunc 會泄漏子項和孫子項,直到父項被取消或計時器觸發。 go vet 工具檢查是否在全部控制流路徑上使用了 CancelFuncs。安全

使用 Contexts 的程序應遵循這些規則,以保持各個包的接口一致,並啓用靜態分析工具來檢查上下文的傳遞:框架

不要將 Contexts 存儲在結構類型中;相反,要將 Context 明確地傳遞給須要它的每一個函數。 Context 應該是第一個參數,一般命名爲 ctx:函數

func DoSomething(ctx context.Context, arg Arg) error {
	// ... use ctx ...
}
複製代碼

即便函數容許,也不要傳遞 nil Context。若是你不肯定要使用哪一個 Context,請傳遞 context.TODO。工具

僅將上下文的值用於 API 邊界和進程之間的請求做用域數據,而不是將可選參數傳遞給函數。學習

能夠將相同的 Context 傳遞給在不一樣 goroutine 中運行的函數;Contexts 對於同時使用多個 goroutine 是安全的。測試

有關服務中使用 Contexts 的示例代碼,請參考blog.golang.org/contextui

index

變量

Canceled 是上下文取消時,經過 Context.Err 返回的錯誤。

var Canceled = errors.New("context canceled")
複製代碼

DeadlineExceeded 是在上下文超過截止時間時,經過 Context.Err 返回的錯誤。

var DeadlineExceeded error = deadlineExceededError{}
複製代碼

函數 WithCancel

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) 複製代碼

WithCancel 返回帶有新 Done channel 的父副本。返回的上下文的 Done channel 在調用返回的取消函數或父上下文的 Done channel 關閉時關閉,取決於誰先發生。

取消此上下文會釋放與其相關的資源,所以代碼應在此上下文中的操做完成後當即調用 cancel。

示例

此示例演示了使用可取消的上下文來防止 goroutine 泄漏。在示例函數的最後,gen 啓動的 goroutine 將返回,而且不會形成 goroutine 泄漏。

package main

import (
	"context"
	"fmt"
)

func main() {
	// gen 在單獨的 goroutine 中生成整數並將它們發送到返回的 channel。
	// 一旦消費了生成的整數,gen 的調用者須要取消上下文,從而不會泄漏 gen 啓動的內部 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() // 當咱們消費完整數後調用取消函數

	for n := range gen(ctx) {
		fmt.Println(n)
		if n == 5 {
			break
		}
	}
}
複製代碼

Run in playground

函數 WithDeadline

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) 複製代碼

WithDeadline 返回父上下文的副本,其截止日期調整爲不遲於 d。若是父級的截止日期早於 d,則 WithDeadline(parent, d)在語義上等同於 parent。返回的上下文的 Done channel 在超過截止時間後,調用返回的取消函數時或父上下文的 Done channel 關閉時關閉,三者取決於誰先發生。

取消此上下文會釋放與其關聯的資源,所以代碼應在此上下文中的操做完成後當即調用 cancel。

示例

這個例子傳遞一個帶有任意截止時間的上下文來告訴一個阻塞的函數它應該在超時的時候丟棄它的任務。

package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	d := time.Now().Add(50 * time.Millisecond)
	ctx, cancel := context.WithDeadline(context.Background(), d)

	// 即便 ctx 將要過時,在任何狀況下要好也最調用它的取消函數。
	// 若是不這樣作,可能會使上下文及其父級的活動時間超過必要時間。
	defer cancel()

	select {
	case <-time.After(1 * time.Second):
		fmt.Println("overslept")
	case <-ctx.Done():
		fmt.Println(ctx.Err())
	}

}
複製代碼

Run in playground

函數 WithTimeout

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) 複製代碼

WithTimeout 返回 WithDeadline(parent, time.Now().Add(timeout))。

取消此上下文會釋放與其關聯的資源,所以代碼應在此上下文中運行的操做完成後當即調用 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)
}
複製代碼
示例

此示例傳遞具備超時的上下文,以告知一個阻塞的函數在超時後它應該丟棄它的任務。

package main

import (
	"context"
	"fmt"
	"time"
)

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()) //打印 "context deadline exceeded"
	}

}
複製代碼

Run in playground

類型 CancelFunc

CancelFunc 通知一個操做丟棄它的任務。 CancelFunc 不等待任務中止。第一次調用後,CancelFunc 的後續調用將失效。

type CancelFunc func() 複製代碼

類型 Context

一個 Context 能夠跨 API 邊界傳遞截止日期,取消信號和其餘值。

Context 的方法能夠由多個 goroutine 同時調用。

type Context interface {
        // Deadline 返回完成的任務的時間,即取消此上下文的時間。
        // 若是沒有設置截止時間,Deadline 返回 ok == false。
        // 對截止日期的連續調用返回相同的結果。
        Deadline() (deadline time.Time, ok bool)

        // 當任務完成時,即此上下文被取消,Done 會返回一個關閉的channel。
        // 若是此上下文一直不被取消,Done 返回 nil。對 Done 的連續調用會返回相同的值。
        //
        // 當取消函數被調用時,WithCancel 使 Done 關閉; 
        // 在截止時間到期時,WithDeadline 使 Done 關閉;
        // 當超時的時候,WithTimeout使 Done 關閉。
        //
        // Done 可使用 select 語句:
        //
        // // Stream 使用 DoSomething 生成值並將它們發送到 out,
        // // 直到 DoSomething 返回錯誤或 ctx.Done 關閉。
        // 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 得到更多關於怎麼使用 Done channel 去取消的例子
        Done() <-chan struct{}

        // 若是 Done 還沒有關閉,則 Err 返回 nil。
        // 若是 Done 關閉,Err 會返回一個非nil的錯誤,緣由:
        // 若是上下文被取消,則調用 Canceled;
        // 若是上下文的截止時間已過,則調用 DeadlineExceeded。
        // 在 Err 返回非 nil 錯誤後,對 Err 的連續調用返回相同的錯誤。
        Err() error

        // Value 返回與此上下文關聯的 key 的值,若是沒有值與 key 關聯,則返回nil。使用相同的 key 連續調用 Value 會返回相同的結果。
        //
        // 僅將上下文的值用於API邊界和進程之間的請求做用域數據,而不是將可選參數傳遞給函數。
        //
        // key 標識上下文中的特定值。
        // 在上下文中存儲值的函數一般在全局變量中分配一個 key,而後使用該 key 做爲 context.WithValue 和 Context.Value 的參數。
        // 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{}
}
複製代碼

函數 Background

func Background() Context 複製代碼

Background 返回一個非 nil 的空 Context。它永遠不會被取消,沒有值,也沒有截止時間。它一般由 main 函數初始化和測試使用,並做爲請求的頂級 Context。

函數 TODO

func TODO() Context 複製代碼

TODO 返回一個非 nil 的空 Context。當不清楚使用哪一個 Context 或者它還不可用時(由於周圍的函數還沒有擴展爲接受 Context 參數),代碼應該使用 context.TODO。

函數 WithValue

func WithValue(parent Context, key, val interface{}) Context 複製代碼

WithValue 返回父級的副本,其中與 key 關聯的值爲 val。

僅將上下文的值用於 API 邊界和進程之間的請求做用域數據,而不是將可選參數傳遞給函數。

提供的 key 必須是可比較的,不該該是字符串類型或任何其餘內置類型,以免使用上下文的包之間產生衝突。 WithValue 的使用者應該爲 keys 定義他們本身的自定義類型。爲了不在分配 interface{}時指定,上下文的 keys 一般具備具體類型 struct {}。或者,導出的上下文的 key 變量的靜態類型應該是指針或接口。

示例

此示例展現如何將值傳遞給上下文以及如何檢索它(若是存在)。

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"))

}
複製代碼

Run in playground

相關文章
相關標籤/搜索