Go併發之Context篇

Go併發之Context篇

 

前言介紹:在學習Go併發的時候,我們總是能夠看到context,而這個context卻只在go語言中存在。筆者在看到context的時候,便問了自己幾個問題。

context是什麼?是用來幹什麼的?我們爲什麼需要context? context是如何使用的?它爲什麼被設計成這個樣子?

基於上面的這些問題,筆者做了整理,筆者覺得在知道了這些問題的答案之後,context 也算是有一點了解了。

1. 爲什麼Go需要context,它是用來幹什麼的?

原因:在golang中的創建一個新的協程並不會返回像c語言創建一個線程一樣類似的pid,這樣就導致我們不能從外部殺死某個線程,所以我們就得讓它自己結束。(備註:goroutine不能返回pid的原因,應該是協程的實現原理有很大關係,多個協程對應1個線程的實現機制。)

當然我們可以採用channel+select的方式,來解決這個問題,不過場景很複雜的時候,我們就需要花費很大的精力去維護channel與這些協程之間的關係,這就導致了我們的併發代碼變得很難維護和管理。例如:由一個請求衍生出多個協程,並且之間需要滿足一定的約束關係,以實現一些諸如:有效期,中止線程樹,傳遞請求全局變量之類的功能。

Context機制:context的產生,正是因爲協程的管理問題,golang官方從1.7之後引入了context,用來專門管理協程之間的關係。

Google的解決方法是Context機制,相互調用的goroutine之間通過傳遞context變量保持關聯,這樣在不用暴露各goroutine內部實現細節的前提下,有效地控制各goroutine的運行。通過傳遞context就可以追蹤goroutine  調用樹,並在這些調用樹之間傳遞通知和元數據。

雖然goroutine之間是平行的,沒有繼承關係,但是Context設計成是包含父子關係的,這樣可以更好的描述goroutine調用之間的樹型關係。

2. context的定義是什麼樣子的?

context是上下文的意思,一般理解爲程序單元的一個運行狀態、現場、快照,其中包含函數調用以及涉及的相關的變量值。

每個Goroutine在執行之前,都要先知道程序當前的執行狀態,通常將這些執行狀態封裝在一個Context變量中,傳遞給要執行的Goroutine中。上下文則幾乎已經成爲傳遞與請求同生存週期變量的標準方法。

下面是https://golang.org/pkg/context/中提供的接口和常用API:

2.1 接口簡介

1. context包裏的方法是線程安全的,可以被多個線程使用。

2.當Context被canceled或是timeout, Done返回一個被closed 的channel。

3.在Done的channel被closed後, Err代表被關閉的原因如果存在。

4.Deadline 返回Context將要關閉的時間。

5.如果存在,Value 返回與 key 相關了的值,不存在返回 nil。

2.2 常用API

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) // 一旦調用cancel,就會取消創建的ctx

func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) // 帶有效期的cancel, 到期之後會主動調用cancel

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) // 帶超時時間的cancel,與WithDeadline類似,區別在於下面的參數是時間間隔

3.我們如何正確使用context?

step1:首先,需要創建context的根節點。

// 返回一個空的Context,它作爲所有由此繼承Context的根節點

func Background() Context  (備註:這裏創建的ctx,不能被取消、沒有值、也沒有過期時間,通常是在主協程或者第一個處理Request的協程中)

step2: 創建下層context節點。

通過2.2中的API來創建下層context節點,而這裏創建好的下層context節點具有以下特點:

1.父節點Context可以主動通過調用cancel方法取消子節點Context。

2.子節點Context只能被動等待。

3.父節點Context自身一旦被取消(如其上級節點Cancel),其下的所有子節點Context均會自動被取消。

例子1:  主協程主動調用cancel() 取消子context

Output:

通過輸出我們可以看出來,在主協程調用了cancel()之後,子協程中的ctx會被主動關閉掉,延遲時間是1秒,會看到打印done。

例子2: 超時之後,調用cancle()的例子

通過輸出可以看出來,在2s超時之後,也就是done會主動打印出來,表明cancel()被主動調用了。(備註:warning提示可以被忽略掉,因爲cancle()不被創建根ctx的協程主動調用就會提示這個告警。)

4.context是如何實現的呢?

(圖片來自:https://zhuanlan.zhihu.com/p/34417106)

1. context的存儲與查詢:

context上下文數據的存儲就像一個樹,每個結點只存儲一個key/value對。WithValue()保存一個key/value對,它將父context嵌入到新的子context,並在節點中保存了key/value數據。Value()查詢key對應的value數據,會從當前context中查詢,如果查不到,會遞歸查詢父context中的數據。

備註:context中的上下文數據不是全局的,它只查詢本節點及父節點們的數據,不能查詢兄弟節點的數據。

2. cancel的實現:

(圖片來自:https://zhuanlan.zhihu.com/p/34417106)

cancelCtx結構體中children保存它的所有子canceler, 當外部觸發cancel時,會調用children中的所有cancel()來終止所有的cancelCtx。done用來標識是否已被cancel。當外部觸發cancel、或者父Context的channel關閉時,此done也會關閉。

對於超時調用cancel(), 是因爲timerCtx 中存儲了一個超時時間,等到超時間到期之後,會主動調用cancel()。

調用cancel()之後的效果如下所示:

(圖片來自:https://zhuanlan.zhihu.com/p/34417106)

 


參考資料:
理解 Go Context 機制:https://juejin.im/entry/58088180c4c971005879b184

Go進階01:golang context 用法詳解:https://mojotv.cn/2018/12/26/what-is-context-in-go

Golang之Context的使用:http://www.nljb.net/default/Golang%E4%B9%8BContext%E7%9A%84%E4%BD%BF%E7%94%A8/

golang中Context的使用場景:https://www.cnblogs.com/yjf512/p/10399190.html

Go Context的踩坑經歷:https://zhuanlan.zhihu.com/p/34417106

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章