【Go進(jìn)階—并發(fā)編程】Context
作者:與昊
來源:SegmentFault 思否社區(qū)
Context 是 Go 應(yīng)用開發(fā)常用的并發(fā)控制技術(shù),它與 WaitGroup 最大的不同點(diǎn)是 Context 對(duì)于派生 goroutine 有更強(qiáng)的控制力,它可以控制多級(jí)的 goroutine。
盡管有很多的爭(zhēng)議,但是在很多場(chǎng)景下使用 Context 都很方便,所以現(xiàn)在它已經(jīng)在 Go 生態(tài)圈中傳播開來了,包括很多的 Web 應(yīng)用框架,都切換成了標(biāo)準(zhǔn)庫的 Context。標(biāo)準(zhǔn)庫中的 database/sql、os/exec、net、net/http 等包中都使用到了 Context。而且,如果遇到了下面的一些場(chǎng)景,也可以考慮使用 Context:
上下文信息傳遞 ,比如處理 http 請(qǐng)求、在請(qǐng)求處理鏈路上傳遞信息;
控制子 goroutine 的運(yùn)行;
超時(shí)控制的方法調(diào)用;
可以取消的方法調(diào)用。
實(shí)現(xiàn)原理
接口定義
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
context.Background():返回一個(gè)非 nil 的、空的 Context,沒有任何值,不會(huì)被 cancel,不會(huì)超時(shí),沒有截止日期。一般用在主函數(shù)、初始化、測(cè)試以及創(chuàng)建根 Context 的時(shí)候。
context.TODO():返回一個(gè)非 nil 的、空的 Context,沒有任何值,不會(huì)被 cancel,不會(huì)超時(shí),沒有截止日期。當(dāng)你不清楚是否該用 Context,或者目前還不知道要傳遞一些什么上下文信息的時(shí)候,就可以使用這個(gè)方法。
一般函數(shù)使用 Context 的時(shí)候,會(huì)把這個(gè)參數(shù)放在第一個(gè)參數(shù)的位置。
從來不把 nil 當(dāng)做 Context 類型的參數(shù)值,可以使用 context.Background() 創(chuàng)建一個(gè)空的上下文對(duì)象,也不要使用 nil。
Context 只用來臨時(shí)做函數(shù)之間的上下文透?jìng)?,不能持久?Context 或者把 Context 長(zhǎng)久保存。把 Context 持久化到數(shù)據(jù)庫、本地文件或者全局變量、緩存中都是錯(cuò)誤的用法。
key 的類型不推薦字符串類型或者其它內(nèi)建類型,否則容易在包之間使用 Context 時(shí)候產(chǎn)生沖突。使用 WithValue 時(shí),key 的類型應(yīng)該是自己定義的類型。
常常使用 struct{} 作為底層類型定義 key 的類型。對(duì)于 exported key 的靜態(tài)類型,常常是接口或者指針。這樣可以盡量減少內(nèi)存分配。
cancelCtx
type cancelCtx struct {
Context
mu sync.Mutex // 互斥鎖
done atomic.Value // 調(diào)用 cancel 時(shí)會(huì)關(guān)閉的 channel
children map[canceler]struct{} // 記錄了由此 Context 派生的所有 child,此 Context 被 cancel 時(shí)會(huì)同時(shí) cancel 所有 child
err error // 錯(cuò)誤信息
}
WithCancel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{Context: parent}
}
func propagateCancel(parent Context, child canceler) {
done := parent.Done()
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock()
if p.err != nil {
// parent 已經(jīng)取消了,直接取消子 Context
child.cancel(false, p.err)
} else {
// 將 child 添加到 parent 的 children 切片
if p.children == nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
atomic.AddInt32(&goroutines, +1)
// 沒有 parent 可以“掛載”,啟動(dòng)一個(gè) goroutine 監(jiān)聽 parent 的 cancel,同時(shí) cancel 自身
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
cancel
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
c.mu.Lock()
// 設(shè)置 cancel 的原因
c.err = err
// 關(guān)閉自身的 done 通道
d, _ := c.done.Load().(chan struct{})
if d == nil {
c.done.Store(closedchan)
} else {
close(d)
}
// 遍歷所有 children,逐個(gè)調(diào)用 cancel 方法
for child := range c.children {
child.cancel(false, err)
}
c.children = nil
c.mu.Unlock()
// 正常情況下,需要將自身從 parent 的 children 切片中刪除
if removeFromParent {
removeChild(c.Context, c)
}
}
timerCtx
type timerCtx struct {
cancelCtx
timer *time.Timer
deadline time.Time
}
WithDeadline
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
// 如果parent的截止時(shí)間更早,直接返回一個(gè)cancelCtx即可
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
propagateCancel(parent, c) // 同cancelCtx的處理邏輯
dur := time.Until(d)
if dur <= 0 { //當(dāng)前時(shí)間已經(jīng)超過了截止時(shí)間,直接cancel
c.cancel(true, DeadlineExceeded)
return c, func() { c.cancel(false, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
// 設(shè)置一個(gè)定時(shí)器,到截止時(shí)間后取消
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}
截止時(shí)間到了;
cancel 函數(shù)被調(diào)用;
parent 的 Done 被 close。
valueCtx
type valueCtx struct {
Context
key, val interface{}
}

