Golang源碼分析(一) Context

context是go應(yīng)用開發(fā)常用的并發(fā)控制技術(shù),它與WaitGroup最大的不同點(diǎn)是context對于派生goroutine有更強(qiáng)的控制力,它可以控制多級的goroutine。

context翻譯成中文是”上下文”,即它可以控制一組呈樹狀結(jié)構(gòu)的goroutine,每個(gè)goroutine擁有相同的上下文。

主要有4個(gè)struct體,都繼承于有4個(gè)方法的Context接口,4個(gè)方法分別是Done,Err,DeadLine,Value。

四個(gè)struct體 分別是emptyCtx、cancelCtx、timerCtx、valueCtx,而timerCtx中復(fù)用了cancelCtx的Context方法,這也是go常用復(fù)用代碼手段。

image.png
type cancelCtx struct {
    Context

    mu       sync.Mutex            // protects following fields
    done     chan struct{}         // created lazily, closed by first cancel call
    children map[canceler]struct{} // set to nil by the first cancel call
    err      error                 // set to non-nil by the first cancel call
}

正是使用了 sync.Mutex ,所以context是線程安全的。且繼承并實(shí)現(xiàn)了Context接口的四個(gè)方法。先來看下cancel

func (c *cancelCtx) cancel(removeFromParent bool, err error) {
    if err == nil {
        panic("context: internal error: missing cancel error")
    }
    c.mu.Lock()
    if c.err != nil {
        c.mu.Unlock()
        return // already canceled
    }
    c.err = err
    if c.done == nil {
        c.done = closedchan
    } else {
        close(c.done)
    }
    for child := range c.children {
        // NOTE: acquiring the child's lock while holding parent's lock.
        child.cancel(false, err)
    }
    c.children = nil
    c.mu.Unlock()

    if removeFromParent {
        removeChild(c.Context, c)
    }
}

這里主要實(shí)現(xiàn)的是關(guān)閉自己和后代,刪除存儲在children。

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

首先初始化了cancelctx,把自己追加到上一級的children里,并返回當(dāng)前cancelctx和cancel。

func propagateCancel(parent Context, child canceler) {
    done := parent.Done()
    if done == nil {
        return // parent is never canceled
    }

    select {
    case <-done:
        // parent is already canceled
        child.cancel(false, parent.Err())
        return
    default:
    }

    if p, ok := parentCancelCtx(parent); ok {
        p.mu.Lock()
        if p.err != nil {
            // parent has already been canceled
            child.cancel(false, p.err)
        } else {
            if p.children == nil {
                p.children = make(map[canceler]struct{})
            }
            p.children[child] = struct{}{}
        }
        p.mu.Unlock()
    } else {
        atomic.AddInt32(&goroutines, +1)
        go func() {
            select {
            case <-parent.Done():
                child.cancel(false, parent.Err())
            case <-child.Done():
            }
        }()
    }
}

propagateCancel 實(shí)現(xiàn)了
1.如果父級ctx已經(jīng)關(guān)閉了,初始化當(dāng)前子ctx的cancel
2.將實(shí)例化過子ctx加入父級的ctx的children 中
3.通過原子操作實(shí)現(xiàn)初始化當(dāng)前子ctx的cancel

func parentCancelCtx(parent Context) (*cancelCtx, bool) {
    done := parent.Done()
    if done == closedchan || done == nil {
        return nil, false
    }
    p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
    if !ok {
        return nil, false
    }
    p.mu.Lock()
    ok = p.done == done
    p.mu.Unlock()
    if !ok {
        return nil, false
    }
    return p, true
}

parentCancelCtx返回子ctx實(shí)例和是否成功將子ctx加入children

總結(jié)
1.Context僅僅是一個(gè)接口定義,根據(jù)實(shí)現(xiàn)的不同,可以衍生出不同的context類型;
2.cancelCtx實(shí)現(xiàn)了Context接口,通過WithCancel()創(chuàng)建cancelCtx實(shí)例;
3.timerCtx實(shí)現(xiàn)了Context接口,通過WithDeadline()和WithTimeout()創(chuàng)建timerCtx實(shí)例;
4.valueCtx實(shí)現(xiàn)了Context接口,通過WithValue()創(chuàng)建valueCtx實(shí)例;
5.三種context實(shí)例可互為父節(jié)點(diǎn),從而可以組合成不同的應(yīng)用形式;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容