Go context源碼解析

在上一篇文章 golang context初探 中,已經(jīng)初步了解了context的用法以及應(yīng)用的場景。那么接下來深入到源碼中來學(xué)習(xí)一下context是怎么實(shí)現(xiàn)的。

emptyCtx

context包的代碼很少,一個(gè)context.go文件,總共才480行代碼,其中還包括大量的注釋。context包首先定義了一個(gè)Context接口:

type Context interface {
    Deadline() (deadline time.Time, ok bool)

    Done() <-chan struct{}

    Err() error

    Value(key interface{}) interface{}
}

接下來定義了一個(gè)emptyCtx類型:

// An emptyCtx is never canceled, has no values, and has no deadline. It is not
// struct{}, since vars of this type must have distinct addresses.
type emptyCtx int

為什么叫做emptyCtx呢?注釋說了emptyCtx不能被取消,沒有值,也沒有deadline。同時(shí),emptyCtx也不是一個(gè)struct{},因?yàn)檫@種類型地變量需要有不同的地址。
這個(gè)emptyCtx實(shí)現(xiàn)了Context接口:

func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
    return
}

func (*emptyCtx) Done() <-chan struct{} {
    return nil
}

func (*emptyCtx) Err() error {
    return nil
}

func (*emptyCtx) Value(key interface{}) interface{} {
    return nil
}

func (e *emptyCtx) String() string {
    switch e {
    case background:
        return "context.Background"
    case todo:
        return "context.TODO"
    }
    return "unknown empty Context"
}

看了上面這段代碼就知道為什么emptyCtx不能被取消,沒有值,也沒有deadline了,因?yàn)樯厦娴膶?shí)現(xiàn)都是直接return。那么,這個(gè)emptyCtx有什么用呢?還記得Background()TODO()函數(shù)嗎?對的,它們的內(nèi)部就是直接返回emptyCtx類型的指針:

var (
    background = new(emptyCtx)
    todo       = new(emptyCtx)
)

func Background() Context {
    return background
}

func TODO() Context {
    return todo
}

所以這兩個(gè)函數(shù)一般是用在main函數(shù)、初始化、測試以及頂層請求的Context。OK,繼續(xù)往下看。
既然emptyCtx類型什么都不做,那么應(yīng)該有其他的類型來實(shí)現(xiàn)相關(guān)的功能才對,也就是cancelCtx,timerCtx,valueCtx三種類型。下面來講一下這三種類型。

Cancel

在之前的文章中我們知道,WithCancelWithTimeout,WithDeadline這三個(gè)方法會返回一個(gè)CancelFunc類型的函數(shù),在Context內(nèi)部就定義了canceler接口:

type canceler interface {
    cancel(removeFromParent bool, err error)
    Done() <-chan struct{}
}

canceler是一種可以直接被取消的context類型,待會繼續(xù)往下看我們會發(fā)現(xiàn),cancelCtxtimerCtx不單單實(shí)現(xiàn)了Context接口(通過匿名成員變量),也實(shí)現(xiàn)了canceler接口。

cancelCtx

cancelCtx的結(jié)構(gòu)體定義:

type cancelCtx struct {
    Context

    done chan struct{} // closed by the first cancel call.

    mu       sync.Mutex
    children map[canceler]struct{} // set to nil by the first cancel call
    err      error                 // set to non-nil by the first cancel call
}

方法集:

func (c *cancelCtx) Done() <-chan struct{} {
    return c.done
}

func (c *cancelCtx) Err() error {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.err
}

func (c *cancelCtx) String() string {
    return fmt.Sprintf("%v.WithCancel", c.Context)
}

// cancel closes c.done, cancels each of c's children, and, if
// removeFromParent is true, removes c from its parent's children.
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
    // 關(guān)閉c的done channel,所有監(jiān)聽c.Done()的goroutine都會收到消息
    close(c.done)
    // 取消child,由于是map結(jié)構(gòu),所以取消的順序是不固定的
    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()
    // 從c.children中移除取消過的child
    if removeFromParent {
        removeChild(c.Context, c)
    }
}

當(dāng)我們在調(diào)用WithCancel的時(shí)候,實(shí)際上返回的就是一個(gè)cancelCtx指針和cancel()方法:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
    c := newCancelCtx(parent)
    propagateCancel(parent, &c)
    return &c, func() { c.cancel(true, Canceled) }
}

// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {
    return cancelCtx{
        Context: parent,
        done:    make(chan struct{}),
    }
}

那么,propagateCancel函數(shù)又是干什么的呢?

// 向上找到最近的可以被取消的父context,將子context放入parent.Children中
// propagateCancel arranges for child to be canceled when parent is.
func propagateCancel(parent Context, child canceler) {
    if parent.Done() == nil {
        return // parent is never canceled
    }
    // 判斷返回的parent是否是cancelCtx
    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 {
        go func() {
            select {
            case <-parent.Done():
                child.cancel(false, parent.Err())
            case <-child.Done():
            }
        }()
    }
}

// parentCancelCtx follows a chain of parent references until it finds a
// *cancelCtx. This function understands how each of the concrete types in this
// package represents its parent.
// 不停地向上尋找最近的可取消的父context
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
    for {
        switch c := parent.(type) {
        case *cancelCtx:
            return c, true
        case *timerCtx:
            return &c.cancelCtx, true
        case *valueCtx:
            parent = c.Context
        default:
            return nil, false
        }
    }
}

Timer

WithTimeoutWithDeadline其實(shí)是差不多的,只是源碼內(nèi)部幫我們封裝了一下:

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
    return WithDeadline(parent, time.Now().Add(timeout))
}

func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
    // 當(dāng)前的deadline比新的deadline還要早,直接返回
    if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
        // The current deadline is already sooner than the new one.
        return WithCancel(parent)
    }
    c := &timerCtx{
        cancelCtx: newCancelCtx(parent),
        deadline:  deadline,
    }
    propagateCancel(parent, c)
    d := time.Until(deadline)
    // deadline已經(jīng)過了,不再設(shè)置定時(shí)器
    if d <= 0 {
        c.cancel(true, DeadlineExceeded) // deadline has already passed
        return c, func() { c.cancel(true, Canceled) }
    }
    c.mu.Lock()
    defer c.mu.Unlock()
    if c.err == nil {
        // 設(shè)定d時(shí)間后執(zhí)行取消方法
        c.timer = time.AfterFunc(d, func() {
            c.cancel(true, DeadlineExceeded)
        })
    }
    return c, func() { c.cancel(true, Canceled) }
}

timerCtx的代碼也實(shí)現(xiàn)地比較簡潔:

type timerCtx struct {
    cancelCtx
    timer *time.Timer // Under cancelCtx.mu.

    deadline time.Time
}

func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
    return c.deadline, true
}

func (c *timerCtx) String() string {
    return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, time.Until(c.deadline))
}

func (c *timerCtx) cancel(removeFromParent bool, err error) {
    c.cancelCtx.cancel(false, err)
    if removeFromParent {
        // Remove this timerCtx from its parent cancelCtx's children.
        removeChild(c.cancelCtx.Context, c)
    }
    c.mu.Lock()
    if c.timer != nil {
        c.timer.Stop()
        c.timer = nil
    }
    c.mu.Unlock()
}

這里需要注意的是,timerCtx并沒有直接實(shí)現(xiàn)了canceler接口,而是使用了匿名成員變量,這樣就可以不用重頭實(shí)現(xiàn)一遍Context接口,而是按需只實(shí)現(xiàn)了Deadline方法。timerCtxcancel方法先調(diào)用了cancelCtxcancel方法,然后再去停止定時(shí)器。

Value

先來看看valueCtx的定義:

type valueCtx struct {
    Context
    key, val interface{}
}

func (c *valueCtx) String() string {
    return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
}

func (c *valueCtx) Value(key interface{}) interface{} {
    // 找到了直接返回
    if c.key == key {
        return c.val
    }
    // 向上繼續(xù)查找
    return c.Context.Value(key)
}

這是最簡單的Context實(shí)現(xiàn)了,在匿名變量Context之外,還增加了兩個(gè)key,value變量來存儲值??纯?code>WithValue的實(shí)現(xiàn):

func WithValue(parent Context, key, val interface{}) Context {
    if key == nil {
        panic("nil key")
    }
    if !reflect.TypeOf(key).Comparable() {
        panic("key is not comparable")
    }
    return &valueCtx{parent, key, val}
}

需要注意的是,key不能是nil并且必須是可比較的,否則就會導(dǎo)致panic!可比較的意思是key不能為函數(shù)類型或者NaN之類的,具體可以看一下reflect包,這里就不細(xì)說了。

最后

整個(gè)context看下來,對context是怎么實(shí)現(xiàn)的也有了清晰的了解,而且context包有大量的測試代碼,非常棒!最近在看go的源碼,發(fā)現(xiàn)真的是一個(gè)很好的學(xué)習(xí)材料,對于如何寫出簡潔明了的代碼是很有幫助的。

最后編輯于
?著作權(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)容

  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程,因...
    小菜c閱讀 7,334評論 0 17
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,803評論 11 349
  • 大一就這樣過去了,時(shí)間非???,我不知道我是如何就這樣沒有學(xué)會什么就過了一年,生活到底是什么?我開始質(zhì)疑,開始反思,...
    Anna聰閱讀 302評論 0 0
  • 今天看的嬰兒篇。 明白0-1歲,1-3歲的孩子要學(xué)習(xí)得內(nèi)容,要完成的使命是什么。 說到依戀是如何來的,多大小孩會出...
    sunny段曉閱讀 210評論 0 0
  • 今天早晨特別困,能聽到客廳里父子倆在對話,但就是爬不起來,干脆就躺著,倆人誰也不干擾我,就這樣任由我安靜地躺著,直...
    葦絮輕揚(yáng)閱讀 246評論 0 1

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