面試官:Context攜帶資料是執行緒安全的嗎?

asong發表於2022-02-11

原文連結:面試官:Context攜帶資料是執行緒安全的嗎?

前言

哈嘍,大家好,我是asong。最近一個群裡看到一個有趣的八股文,問題是:使用context攜帶的value是執行緒安全的嗎?這道題其實就是考察面試者對context實現原理的理解,如果不知道context的實現原理,很容易答錯這道題,所以本文我們就藉著這道題,再重新理解一遍context攜帶value的實現原理。

context攜帶value是執行緒安全的嗎?

先說答案,context本身就是執行緒安全的,所以context攜帶value也是執行緒安全的,寫個簡單例子驗證一下:

func main()  {
    ctx := context.WithValue(context.Background(), "asong", "test01")
    go func() {
        for {
            _ = context.WithValue(ctx, "asong", "test02")
        }
    }()
    go func() {
        for {
            _ = context.WithValue(ctx, "asong", "test03")
        }
    }()
    go func() {
        for {
            fmt.Println(ctx.Value("asong"))
        }
    }()
    go func() {
        for {
            fmt.Println(ctx.Value("asong"))
        }
    }()
    time.Sleep(10 * time.Second)
}

程式正常執行,沒有任何問題。
但是context對攜帶的資料沒有型別限制,所以任何資料型別都是用context攜帶,在攜帶的資料型別是指標型別時,就不是執行緒安全的,來看一個例子:

func main()  {
    m := make(map[string]string)
    m ["asong"] = "Golang夢工廠"
    ctx := context.WithValue(context.Background(), "asong", m)
    go func() {
        for {
            m1 := ctx.Value("asong")
            mm := m1.(map[string]string)
            mm["asong"] = "123213"
        }
    }()
    go func() {
        for {
            m1 := ctx.Value("asong")
            mm := m1.(map[string]string)
            mm["asong"] = "123213"
        }
    }()
    time.Sleep(10 * time.Second)
}

執行結果:

fatal error: concurrent map writes

goroutine 18 [running]:
runtime.throw({0x1072af2, 0x0})
......

為什麼執行緒安全?

context包提供兩種建立根context的方式:

  • context.Backgroud()
  • context.TODO()

又提供了四個函式基於父Context衍生,其中使用WithValue函式來衍生context並攜帶資料,每次呼叫WithValue函式都會基於當前context衍生一個新的子contextWithValue內部主要就是呼叫valueCtx類:

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

valueCtx結構如下:

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

valueCtx繼承父Context,這種是採用匿名介面的繼承實現方式,key,val用來儲存攜帶的鍵值對。

通過上面的程式碼分析,可以看到新增鍵值對不是在原context結構體上直接新增,而是以此context作為父節點,重新建立一個新的valueCtx子節點,將鍵值對新增在子節點上,由此形成一條context鏈。

獲取鍵值過程也是層層向上呼叫直到最終的根節點,中間要是找到了key就會返回,否會就會找到最終的emptyCtx返回nil

畫個圖表示一下:

image-20220207214507921

總結:context新增的鍵值對一個鏈式的,會不斷衍生新的context,所以context本身是不可變的,因此是執行緒安全的,但是如果我們攜帶的資料是指標型別,這時依然有執行緒不安全的風險。

總結

本文主要是想帶大家回顧一下context的實現原理,面試中面試官都喜歡隱晦提出問題,所以這就需要我們有很紮實的基本功,一不小心就會掉入面試官的陷阱,要處處小心哦~

好啦,本文到這裡就結束了,我是asong,我們下期見。

歡迎關注公眾號:【Golang夢工廠】

相關文章