天天看點

Golang context 包入門

golang 的 context package 提供了一種簡潔又強大方式來管理 goroutine 的生命周期,同時提供了一種 requst-scope k-v store。但是對于新手來說,context 的概念不算非常的直覺,這篇文章來帶領大家了解一下 context 包的基本作用和使用方法。

在 go1.7 及以上版本 context 包被正式列入官方庫中,是以我們隻需要<code>import "context"</code>就可以了,而在 go1.6 及以下版本,我們要 <code>import "golang.org/x/net/context"</code>

context interface 是最基本的接口

<code>deadline()</code>傳回一個<code>time.time</code>,是目前 context 的應該結束的時間,ok 表示是否有 deadline

<code>done()</code>傳回一個<code>struct{}</code>類型的隻讀 channel

<code>err()</code>傳回 context 被取消時的錯誤

<code>value(key interface{})</code> 是 context 自帶的 k-v 存儲功能

canceler interface 定義了提供 cancel 函數的 context,當然要求資料結構要同時實作 context interface

除了以上兩個 interface 之外,context 包中還定義了若幹個struct,來實作上面的 interface

emptyctx

<code>emptyctx</code>是空的context,隻實作了<code>context</code> interface,隻能作為 root context 使用。

cancelctx

<code>cancelctx</code>繼承了<code>context</code>并實作了<code>canceler</code>interface,從<code>withcancel()</code>函數産生

timerctx

<code>timerctx</code>繼承了<code>cancelctx</code>,是以也自然實作了<code>context</code>和<code>canceler</code>這兩個interface,由<code>withdeadline()</code>函數産生

valuectx

<code>valuectx</code>包含<code>key</code>、<code>val</code> field,可以儲存一對鍵值對,由<code>withvalue()</code>函數産生

context 隻定義了 interface,真正使用時需要執行個體化,官方首先定義了一個 emptyctx struct 來實作 context interface,然後提供了<code>backgroud()</code>函數來便利的生成一個 emptyctx 執行個體。

實作代碼如下

backgroud() 生成的 emptyctx 執行個體是不能取消的,因為<code>emptyctx</code>沒有實作<code>canceler</code> interface,要正常取消功能的話,還需要對 emptyctx 執行個體進行派生。常見的兩種派生用法是<code>withcancel()</code> 和 <code>withtimeout</code>。

調用<code>withcancel()</code>可以将基礎的 context 進行繼承,傳回一個<code>cancelctx</code>示例,并傳回一個函數,可以在外層直接調用<code>cancelctx.cancel()</code>來取消 context

代碼如下:

調用<code>withtimeout</code>,需要傳一個逾時時間。來指定過多長時間後逾時結束 context,源代碼中可以得知<code>withtimeout</code>是<code>withdeadline</code>的一層皮,<code>withdeadline</code>傳的是具體的結束時間點,這個在工程中并不實用,<code>withtimeout</code>會根據運作時的時間做轉換。

源代碼如下:

在<code>withdeadline</code>中,将 timectx.timer 挂上結束時的回調函數,回調函數的内容是調用cancel來結束 context。

<code>withvalue</code>的具體使用方法在下面的用例中會講。

我們起一個本地的 http serice,名字叫"lazy",這個 http server 會随機的發出一些慢請求,要等6秒以上才傳回,我們使用這個程式來模拟我們的被調用方 hang 住的情況

然後我們寫一個主動調用的 http service,他會調用我們剛才寫的"lazy",我們使用 context,來解決超過2秒的慢請求問題,如下代碼:

在 main 函數中,我們定義了一個逾時時間為2秒的 context,傳給真正做事的<code>work()</code>,work接收到這個 ctx 的時候,需要等待 ctx.done() 傳回,因為 channel 關閉的時候,ctx.done() 會受到空值,當 ctx.done()傳回時,就意味着 context 已經逾時結束,要做一些掃尾工作然後 return 即可。

在 golang1.7 中,<code>"net/http"</code>原生支援将context嵌入到 <code>*http.request</code>中,并且提供了<code>http.request.conext()</code> 和 <code>http.request.withcontext()</code>這兩個函數來建立一個 context 和 将 context 加入到一個<code>http.request</code>執行個體中。下面的程式示範了一下利用<code>withvalue()</code>建立一個可以儲存 k-v 的 context,然後寫一個中間件來自動擷取 http頭部的 "x-rquest-id"值,加入到 context 中,使業務函數可以直接取到該值。