原文鏈接: Go 語(yǔ)言 context 都能做什么?
很多 Go 項(xiàng)目的源碼便瑟,在讀的過(guò)程中會(huì)發(fā)現(xiàn)一個(gè)很常見(jiàn)的參數(shù) ctx
震捣,而且基本都是作為函數(shù)的第一個(gè)參數(shù)。
為什么要這么寫(xiě)呢酝锅?這個(gè)參數(shù)到底有什么用呢诡必?帶著這樣的疑問(wèn),我研究了這個(gè)參數(shù)背后的故事搔扁。
開(kāi)局一張圖:
核心是 Context
接口:
// A Context carries a deadline, cancelation signal, and request-scoped values
// across API boundaries. Its methods are safe for simultaneous use by multiple
// goroutines.
type Context interface {
// Done returns a channel that is closed when this Context is canceled
// or times out.
Done() <-chan struct{}
// Err indicates why this context was canceled, after the Done channel
// is closed.
Err() error
// Deadline returns the time when this Context will be canceled, if any.
Deadline() (deadline time.Time, ok bool)
// Value returns the value associated with key or nil if none.
Value(key interface{}) interface{}
}
包含四個(gè)方法:
-
Done()
:返回一個(gè) channel爸舒,當(dāng) times out 或者調(diào)用 cancel 方法時(shí)。 -
Err()
:返回一個(gè)錯(cuò)誤稿蹲,表示取消 ctx 的原因扭勉。 -
Deadline()
:返回截止時(shí)間和一個(gè) bool 值。 -
Value()
:返回 key 對(duì)應(yīng)的值苛聘。
有四個(gè)結(jié)構(gòu)體實(shí)現(xiàn)了這個(gè)接口涂炎,分別是:emptyCtx
, cancelCtx
, timerCtx
和 valueCtx
。
其中 emptyCtx
是空類型设哗,暴露了兩個(gè)方法:
func Background() Context
func TODO() Context
一般情況下唱捣,會(huì)使用 Background()
作為根 ctx,然后在其基礎(chǔ)上再派生出子 ctx网梢。要是不確定使用哪個(gè) ctx爷光,就使用 TODO()
。
另外三個(gè)也分別暴露了對(duì)應(yīng)的方法:
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
遵循規(guī)則
在使用 Context 時(shí)澎粟,要遵循以下四點(diǎn)規(guī)則:
- 不要將 Context 放入結(jié)構(gòu)體蛀序,而是應(yīng)該作為第一個(gè)參數(shù)傳入,命名為
ctx
活烙。 - 即使函數(shù)允許徐裸,也不要傳入
nil
的 Context。如果不知道用哪種 Context啸盏,可以使用context.TODO()
重贺。 - 使用 Context 的 Value 相關(guān)方法只應(yīng)該用于在程序和接口中傳遞和請(qǐng)求相關(guān)的元數(shù)據(jù),不要用它來(lái)傳遞一些可選的參數(shù)回懦。
- 相同的 Context 可以傳遞給不同的 goroutine气笙;Context 是并發(fā)安全的。
WithCancel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
WithCancel
返回帶有新 Done
通道的父級(jí)副本怯晕。當(dāng)調(diào)用返回的 cancel
函數(shù)或關(guān)閉父上下文的 Done
通道時(shí)潜圃,返回的 ctx
的 Done
通道將關(guān)閉。
取消此上下文會(huì)釋放與其關(guān)聯(lián)的資源舟茶,因此在此上下文中運(yùn)行的操作完成后谭期,代碼應(yīng)立即調(diào)用 cancel
堵第。
舉個(gè)例子:
這段代碼演示了如何使用可取消上下文來(lái)防止 goroutine 泄漏。在函數(shù)結(jié)束時(shí)隧出,由 gen
啟動(dòng)的 goroutine 將返回而不會(huì)泄漏踏志。
package main
import (
"context"
"fmt"
)
func main() {
// gen generates integers in a separate goroutine and
// sends them to the returned channel.
// The callers of gen need to cancel the context once
// they are done consuming generated integers not to leak
// the internal goroutine started by gen.
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
}
}
}
輸出:
1
2
3
4
5
WithDeadline
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
WithDeadline
返回父上下文的副本,并將截止日期調(diào)整為不晚于 d
胀瞪。如果父級(jí)的截止日期已經(jīng)早于 d
针余,則 WithDeadline(parent, d)
在語(yǔ)義上等同于 parent
。
當(dāng)截止時(shí)間到期凄诞、調(diào)用返回的取消函數(shù)時(shí)或當(dāng)父上下文的 Done
通道關(guān)閉時(shí)圆雁,返回的上下文的 Done
通道將關(guān)閉。
取消此上下文會(huì)釋放與其關(guān)聯(lián)的資源幔摸,因此在此上下文中運(yùn)行的操作完成后摸柄,代碼應(yīng)立即調(diào)用取消。
舉個(gè)例子:
這段代碼傳遞具有截止時(shí)間的上下文既忆,來(lái)告訴阻塞函數(shù)驱负,它應(yīng)該在到達(dá)截止時(shí)間時(shí)立刻退出。
package main
import (
"context"
"fmt"
"time"
)
const shortDuration = 1 * time.Millisecond
func main() {
d := time.Now().Add(shortDuration)
ctx, cancel := context.WithDeadline(context.Background(), d)
// Even though ctx will be expired, it is good practice to call its
// cancellation 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())
}
}
輸出:
context deadline exceeded
WithTimeout
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
WithTimeout
返回 WithDeadline(parent, time.Now().Add(timeout))
患雇。
取消此上下文會(huì)釋放與其關(guān)聯(lián)的資源跃脊,因此在此上下文中運(yùn)行的操作完成后,代碼應(yīng)立即調(diào)用取消苛吱。
舉個(gè)例子:
這段代碼傳遞帶有超時(shí)的上下文酪术,以告訴阻塞函數(shù)應(yīng)在超時(shí)后退出。
package main
import (
"context"
"fmt"
"time"
)
const shortDuration = 1 * time.Millisecond
func main() {
// Pass a context with a timeout to tell a blocking function that it
// should abandon its work after the timeout elapses.
ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err()) // prints "context deadline exceeded"
}
}
輸出:
context deadline exceeded
WithValue
func WithValue(parent Context, key, val any) Context
WithValue
返回父級(jí)的副本翠储,其中與 key
關(guān)聯(lián)的值為 val
绘雁。
其中鍵必須是可比較的,并且不應(yīng)是字符串類型或任何其他內(nèi)置類型援所,以避免使用上下文的包之間發(fā)生沖突庐舟。 WithValue
的用戶應(yīng)該定義自己的鍵類型。
為了避免分配給 interface{}
住拭,上下文鍵通常具有具體的 struct{}
類型挪略。或者滔岳,導(dǎo)出的上下文鍵變量的靜態(tài)類型應(yīng)該是指針或接口杠娱。
舉個(gè)例子:
這段代碼演示了如何將值傳遞到上下文以及如何檢索它(如果存在)。
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"))
}
輸出:
found value: Go
key not found: color
本文的大部分內(nèi)容谱煤,包括代碼示例都是翻譯自官方文檔摊求,代碼都是經(jīng)過(guò)驗(yàn)證可以執(zhí)行的。如果有不是特別清晰的地方趴俘,可以直接去讀官方文檔睹簇。
以上就是本文的全部?jī)?nèi)容奏赘,如果覺(jué)得還不錯(cuò)的話歡迎點(diǎn)贊寥闪,轉(zhuǎn)發(fā)和關(guān)注太惠,感謝支持。
官方文檔:
源碼分析:
- https://mritd.com/2021/06/27/golang-context-source-code/
- https://www.qtmuniao.com/2020/07/12/go-context/
- https://seekload.net/2021/11/28/go-context.html
推薦閱讀: