# Golang 學習筆記 Context 介紹 --- context ctx 在 golang中非常常見,也是相較於其他程式語言中非常獨特的特色 用途:**處理多個 goroutine 的情況,特別是用來送出取消或結束的 signal。** context 可傳入 Goroutine, 再透過 `Done()` 取得 signal,一旦執行Done 的 Channel 關閉,Goroutine便會關閉並 return。 (即 父層的 `Context` 關閉其 Done channel 之後,子層的 Done channel 則會自動關閉。) #### 基本規則 - 傳入context時應為第一個參數 - **不要傳遞nil的context**, `context.TODO()` instead - 應只傳遞 scope 內能見/該請求之資料 (request-scoped data),不要傳入 optional parameters 到函式中。 - 同個 Context 在不同的 goroutine 之間傳遞是 thread safe 的 (換句話說,多個 goroutines 中同時使用 Context 是安全的) ```go func DoSomething(ctx context.Context, arg Arg) error { // ... use ctx ... } ``` Context 方法 --- 1. `context.Background()` : 回傳一個不是 `nil` 的 empty Context。**不會被取消 (canceled)**,**無值、也不會deadline** 2. `context.TODO` : 回傳一個不是 `nil` 的 empty Context。通常在無其他context可用或無法取得時使用。 3. `context.WithCancel()` : 回傳 Context 物件和 CancelFunction。這個 Context 的 Done channel 會在 cancel function 被呼叫到時關閉,或是父層的 Done channel 關閉時亦會關閉。 - 重複呼叫 cancel() 不會有任何效果 - context 建議當成函式或 goroutine 的參數傳入,並且命名為 ctx,並**不建議把它保存在 struct 中** ```go func WithCancel(parent Context) (ctx Context, cancel CancelFunc) ``` example: ```go func main() { root := context.Background() ctx, cancel := context.WithCancel(root) defer cancel() rand.Seed(time.Now().UnixNano()) if rand.Intn(2) == 0 { cancel() // close context } select { case <-ctx.Done(): fmt.Println("context closed") default: fmt.Println("hello") } } ``` 4. `context.WithDeadline()` : 在 `context.WithDeadline()` 中可以指定一個時間(time.Time)當作 deadline,一旦時間到時,就會自動觸發 cancel - context.WithDeadline() 同樣會回傳 cancel,因此也可以主動呼叫 cancel - 如果父層的 context 被 cancel 的話,子層的 context 也會一併被 cancel ```go func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) ``` 5. `context.WithTimeout()` : api常用之超過一定的時間後就會停止該 function ```go func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) ``` - `context.WithTimeout()` 的用法和 `context.WithDeadline()` 幾乎相同,差別只在於 WithTimeout() 帶入的參數是時間區間(time.Duration) - 實際上,`WithTimeout()` 的底層仍然是呼叫 `WithDeadline()` 6. `context.WithValue()` : 在context中以key-value方式帶入傳遞參數 - 注意層層傳遞之順序問題 ```go func WithoutCancel(parent Context) Context ``` example: ```golang func main() { ctx, cancel := context.WithCancel(context.Background()) ctx1 := context.WithValue(ctx, "hello", "world") ctx2 := context.WithValue(ctx1, "foo", "bar") cancel() select { case <-ctx2.Done(): fmt.Println(ctx2.Value("hello")) fmt.Println(ctx2.Value("foo")) } } ``` parent Context為第一參數,第二參數為key,第三個參數val為key對應的值。使用此函式可把資料放入context在goroutine中傳遞。 上面建立三個Context階層依序為ctx -> ctx1 -> ctx2。使用Context.WithValue分別在不同的Context放入值,可看到最末階的ctx2可取得parnet Context塞入的值。此外也可看到parent Context若取消其derived Context也會被關閉。 (此篇為各筆記之整理與個人使用心得,非原創內容)