golang中Context詳解

bytemode · · 165 次點擊 · · 開始瀏覽    
## 什么是Context Context通常被譯作上下文,它是一個比較抽象的概念。一般理解為程序單元的一個運行狀態、現場,上下上下則是存在上下層的傳遞,上會把內容傳遞給下。在Go語言中,程序單元也就指的是Goroutine。 每個Goroutine在執行之前,都要先知道程序當前的執行狀態,通常將這些執行狀態封裝在一個Context變量中,傳遞給要執行的Goroutine中。上下文則幾乎已經成為傳遞與請求同生存周期變量的標準方法。在網絡編程下,當接收到一個網絡請求Request,處理Request時,我們可能需要開啟不同的Goroutine來獲取數據與邏輯處理,即一個請求Request,會在多個Goroutine中處理。而這些Goroutine可能需要共享Request的一些信息;同時當Request被取消或者超時的時候,所有從這個Request創建的所有Goroutine也應該被結束。 ## context包 Go的設計者早考慮多個Goroutine共享數據,以及多Goroutine管理機制。 context包不僅實現了在程序單元之間共享狀態變量的方法,同時能通過簡單的方法,使我們在被調用程序單元的外部,通過設置ctx變量值,將過期或撤銷這些信號傳遞給被調用的程序單元。在網絡編程中,若存在A調用B的API, B再調用C的API,若A調用B取消,那也要取消B調用C,通過在A,B,C的API調用之間傳遞Context,以及判斷其狀態,就能解決此問題。 context包的核心就是Context接口,其定義如下: ```go type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} } ``` Deadline會返回一個超時時間,Goroutine獲得了超時時間后,例如可以對某些io操作設定超時時間。 Done方法返回一個信道(channel),當Context被撤銷或過期時,該信道是關閉的,即它是一個表示Context是否已關閉的信號。 當Done信道關閉后,Err方法表明Context被撤的原因。 Value可以讓Goroutine共享一些數據,當然獲得數據是協程安全的。但使用這些數據的時候要注意同步,比如返回了一個map,而這個map的讀寫則要加鎖。 Context接口沒有提供方法來設置其值和過期時間,也沒有提供方法直接將其自身撤銷。也就是說,Context不能改變和撤銷其自身。那么該怎么通過Context傳遞改變后的狀態呢? ## context使用 無論是Goroutine,他們的創建和調用關系總是像層層調用進行的,就像人的輩分一樣,而更靠頂部的Goroutine應有辦法主動關閉其下屬的Goroutine的執行(不然程序可能就失控了)。為了實現這種關系,Context結構也應該像一棵樹,葉子節點須總是由根節點衍生出來的。 要創建Context樹,第一步就是要得到根節點,context.Background函數的返回值就是根節點: ```go func Background() Context ``` 該函數返回空的Context,該Context一般由接收請求的第一個Goroutine創建,是與進入請求對應的Context根節點,它不能被取消、沒有值、也沒有過期時間。它常常作為處理Request的頂層context存在。 有了根節點,又該怎么創建其它的子節點,孫節點呢?context包為我們提供了多個函數來創建他們: ```go func WithCancel(parent Context) (ctx Context, cancel CancelFunc) func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) func WithValue(parent Context, key interface{}, val interface{}) Context ``` 函數都接收一個Context類型的參數parent,并返回一個Context類型的值,這樣就層層創建出不同的節點。子節點是從復制父節點得到的,并且根據接收參數設定子節點的一些狀態值,接著就可以將子節點傳遞給下層的Goroutine了。 再回到之前的問題:該怎么通過Context傳遞改變后的狀態呢?使用Context的Goroutine無法取消某個操作,其實這也是符合常理的,因為這些Goroutine是被某個父Goroutine創建的,而理應只有父Goroutine可以取消操作。在父Goroutine中可以通過WithCancel方法獲得一個cancel方法,從而獲得cancel的權利。 第一個WithCancel函數,它是將父節點復制到子節點,并且還返回一個額外的CancelFunc函數類型變量,該函數類型的定義為: ```go type CancelFunc func() ``` 調用CancelFunc對象將撤銷對應的Context對象,這就是主動撤銷Context的方法。在父節點的Context所對應的環境中,通過WithCancel函數不僅可創建子節點的Context,同時也獲得了該節點Context的控制權,一旦執行該函數,則該節點Context就結束了,則子節點需要類似如下代碼來判斷是否已結束,并退出該Goroutine: ```go select { case <-cxt.Done(): // do some clean... } ``` WithDeadline函數的作用也差不多,它返回的Context類型值同樣是parent的副本,但其過期時間由deadline和parent的過期時間共同決定。當parent的過期時間早于傳入的deadline時間時,返回的過期時間應與parent相同。父節點過期時,其所有的子孫節點必須同時關閉;反之,返回的父節點的過期時間則為deadline。 WithTimeout函數與WithDeadline類似,只不過它傳入的是從現在開始Context剩余的生命時長。他們都同樣也都返回了所創建的子Context的控制權,一個CancelFunc類型的函數變量。 當頂層的Request請求函數結束后,我們就可以cancel掉某個context,從而層層Goroutine根據判斷cxt.Done()來結束。 WithValue函數,它返回parent的一個副本,調用該副本的Value(key)方法將得到val。這樣我們不光將根節點原有的值保留了,還在子孫節點中加入了新的值,注意若存在Key相同,則會被覆蓋。 ## 小結 context包通過構建樹型關系的Context,來達到上一層Goroutine能對傳遞給下一層Goroutine的控制。對于處理一個Request請求操作,需要采用context來層層控制Goroutine,以及傳遞一些變量來共享。 Context對象的生存周期一般僅為一個請求的處理周期。即針對一個請求創建一個Context變量(它為Context樹結構的根);在請求處理結束后,撤銷此ctx變量,釋放資源。 每次創建一個Goroutine,要么將原有的Context傳遞給Goroutine,要么創建一個子Context并傳遞給Goroutine。 Context能靈活地存儲不同類型、不同數目的值,并且使多個Goroutine安全地讀寫其中的值。 當通過父Context對象創建子Context對象時,可同時獲得子Context的一個撤銷函數,這樣父Context對象的創建環境就獲得了對子Context將要被傳遞到的Goroutine的撤銷權。 ## 使用原則 使用Context的程序包需要遵循如下的原則來滿足接口的一致性以及便于靜態分析。 1. 不要把Context存在一個結構體當中,顯式地傳入函數。Context變量需要作為第一個參數使用,一般命名為ctx; 2. 即使方法允許,也不要傳入一個nil的Context,如果你不確定你要用什么Context的時候傳一個context.TODO; 3. 使用context的Value相關方法只應該用于在程序和接口中傳遞的和請求相關的元數據,不要用它來傳遞一些可選的參數; 4. 同樣的Context可以用來傳遞到不同的goroutine中,Context在多個goroutine中是安全的; 5. 在子Context被傳遞到的goroutine中,應該對該子Context的Done信道(channel)進行監控,一旦該信道被關閉(即上層運行環境撤銷了本goroutine的執行),應主動終止對當前請求信息的處理,釋放資源并返回。

入群交流(和以上內容無關):Go中文網 QQ 交流群:729884609 或加微信入微信群:274768166 備注:入群;關注公眾號:Go語言中文網

165 次點擊  ?  1 贊  
加入收藏 微博
下一篇:golang chan詳解
暫無回復
添加一條新回復 (您需要 登錄 后才能回復 沒有賬號 ?)
  • 請盡量讓自己的回復能夠對別人有幫助
  • 支持 Markdown 格式, **粗體**、~~刪除線~~、`單行代碼`
  • 支持 @ 本站用戶;支持表情(輸入 : 提示),見 Emoji cheat sheet
  • 圖片支持拖拽、截圖粘貼等方式上傳