context
package 主要用在 goroutine 或 API 間的溝通。主要有兩個 root context:
context.TODO()
context.Background()
及四個 function:
WithCancel(parent Context)
WithDeadline(parent Context, d time.Time)
WithTimeout(parent Context, timeout time.Duration)
WithValue(parent Context, key, val interface{})
可以使用 .Done()
取得 channel,並讀取 channel,讓程式停住 (Block),等待 context 被取消 (呼叫 cancel,或 timeout, deadline 到)。
Package context defines the Context type, which carries deadlines, cancelation signals, and other request-scoped values across API boundaries and between processes.
Context
間有從屬關係,當父 context 被取消時,child context 也一併會取消。WithCancel
, WithDeadline
, WithTimeout
都會回傳 cancel function。即使原本的 context 已經被取消(可能是程式主動取消,或者被父 context 取消),一定要呼叫該 context 的 cancel function.
Even though ctx will be expired, it is good practice to call its cancelation function in any case.
使用 context 時,請依循以下規則(官方規則)
-
不要在 struct 內,宣告變數存
Context
,而是要透過 function 的參數傳遞,並且是該 function 的第一個參數, 命名為ctx
。eg:func DoSomething(ctx context.Context, arg Arg) error { // ... use ctx ... }
-
不要傳 nil Context 給 function。如果還不確定要做什麼功能時,可用
context.TODO()
或context.Background()
。 -
Context (
context.withValue
) 只能做 request-socped1 使用,不要將函式需要的參數值,放在裏面。 -
可以在不同的 goroutine 中共用同一個 Context。(Thread-Safe)
@import "ex13_01/main.go" {class="line-numbers"}
結果:
2020/01/16 14:18:41 [Timeout Context] has dealine: 2020-01-16 14:18:44
2020/01/16 14:18:41 [Canncel Context] does not have dealine
2020/01/16 14:18:41 [Deadline Context] has dealine: 2020-01-16 14:18:51
2020/01/16 14:18:44 timeout ...
2020/01/16 14:18:44 cancel error: <nil>
2020/01/16 14:18:44 canncel...
2020/01/16 14:18:44 cancel error: context canceled
2020/01/16 14:18:44 The cancel context has been cancelled...
2020/01/16 14:18:51 The deadline context has been cancelled...
- 使用
.Done
來等待 context 回應。 - 可使用回傳的 cancel 函式,取消 context。
@import "ex13_02/main.go" {class="line-numbers"}
結果:
2020/01/16 14:17:24 cancel 1
2020/01/16 14:17:24 ctx1: context canceled
2020/01/16 14:17:24 ctx2: context canceled
2020/01/16 14:17:24 end
2020/01/16 14:17:24 cancel 2
- 當
ctx1
的 cancel functioncancel1
被呼叫時,childctx2
也會一併被取消 - 即使
ctx2
已經被取消了,也記得呼叫ctx2
的 cancel functioncancel2
。
以下的程式,摸擬有多個 channel 一直接受資料,再匯流到一個 channel 來讀取。當發生 timeout (5 sec.) 時,結果程式。主要是練習 context
及 reflect.Select
。
graph LR
goroutine_1 --> channel_1;
goroutine_2 --> channel_2;
goroutine_3 --> channel_3;
channel_1 --> out;
channel_2 --> out;
channel_3 --> out;
graph LR
TimeoutContext.Done --> reflect.Select;
Channel_1 --> reflect.Select;
Channel_2 --> reflect.Select;
Channel_3 --> reflect.Select;
@import "ex13_03/main.go" {class="line-numbers"}
- 可用使用
reflect.Select
及reflect.SelectCase
來控制不固定數量的 channel。請見ReadAll
.i, v, ok := reflect.Select(cases)
: 如果其中有一個 channel 被關閉時,v 會是 zero-value, ok 會是false
- 當
timeout
Context 發生 timeout 時,reflect.Select
會接到,然後結束 goroutine.
Footnotes
-
類似 JAVA Servlet 中
ServletRuquest.GetAttribute
及ServletRequest.SetAttribute
,當請求結束後,在 Attribute 的資料也被消滅了。 ↩