Go 標準函式庫的context
package簡介。
Go context用來在多個goroutine間傳遞資料、超時訊號及取消訊號。
了解context前要先認識其應用場景。Go server收到一個請求後通常會建立其他goroutine做併發處理來提高效能,例如耗時計算處理、從資料庫取得資料或呼叫外部服務。而多個goroutine間即可利用context來存取共用的請求資料。共用的請求資料包括使用者資料、Auth token、請求限制時間、追蹤編號等。
Context規則
使用context應遵守以下規則:
- 不要在struct的屬性存放
Context
,在函式間傳遞Context
時應明確地放在第一個參數並命名為ctx
。
func DoSomething(ctx context.Context, arg1 Arg, arg2 Arg, ...) error {...}
- 不要傳遞
nil
的Context
參數,若不知道Context可用context.TODO()
替代。
DoSomething(context.TODO(), arg1, arg2, ...)
Context
應只傳遞屬請求範圍的資料(request-scoped data)。- 同個
Context
能在不同的goroutine間傳遞,是執行緒安全的。
Context介面
context
package的核心為Context
介面,定義了以下方法:
Done() <-chan struct{}
- 當Context因取消或超時而關閉返回一個關閉的channel receive代表取消訊號、反之返回nil
,多用在select敘述做goroutine的關閉處理。
ctx, cancel := context.WithCancel(ctx) ... select { case <-ctx.Done(): ... }
Err() error
- 如果Context未關閉返回nil
,反之返回錯誤error
說明關閉原因。
ctx, cancel := context.WithCancel(ctx) err := ctx.Err()
Deadline() (deadline time.Time, ok bool)
- 當Context超時時回傳時間且ok==true
,無超時則回傳ok==false
。
ctx, cancel := context.WithDeadline(ctx, time) time, ok := ctx.Deadline()
Value(key interface{}) interface{}
- 用來取得Conetext中的資料,使用key取得。
ctx := context.WithValue(ctx, "foo", "bar") fmt.Println(ctx.Value("foo")) // bar
建立Context
Root Context
使用context.Background()
建立root Context,此為Context tree的根,root Context無法被取消,沒有deadline且不帶value。其他Context都源於root Context。
main.go
package main
import (
"context"
)
func main() {
root := context.Background() // create a root Context
...
}
Context可形成tree(樹結構),一個Context可衍伸出多個子Context,稱為derived Context。
Derived Context
Derived Context可使用context.WithCancel
、context.WithDeadline
、context.WithTimeout
及context.WithValue
建立,皆必須接收一個parent Context為參數。
WithCancel(parent Context) (ctx Context, cancel CancelFunc)
接收parent Context參數,回傳一個複製parent Context的Context並有新的Done channel及CancelFunc
函式cancel
。
CancelFunc
函式被調用或parent Context的Done channel關閉時,回傳的Context也會關閉。
例如下面當隨機數為0時調用cancel()
關閉Context,則Context.Done()
返回一個關閉的channel並印出"context closed"。
package main
import (
"context"
"fmt"
"math/rand"
"time"
)
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")
}
}
WithDeadline(parent Context, d time.Time) (ctx Context, cancel CancelFunc)
parent Context為第一參數,第二參數為逾時時間,回傳有Done channel的Context及CancelFunc
函式cancel
。
若超過逾時時間、CancelFunc
函式被調用或parent Context的Done channel關閉時,回傳的Context也會關閉。
例如下面執行sleep時若超過context.WithDeadline
設定當下時間後1秒時便自動關閉Context的Done channel。
package main
import (
"context"
"fmt"
"math/rand"
"time"
)
func main() {
root := context.Background()
ctx, cancel := context.WithDeadline(root, time.Now().Add(time.Second*1))
defer cancel()
rand.Seed(time.Now().UnixNano())
d := time.Duration(rand.Intn(3))
fmt.Printf("sleep %d seconds\n", d)
time.Sleep(time.Second * d)
select {
case <-ctx.Done():
fmt.Println("context closed")
default:
fmt.Println("hello")
}
}
WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
相當於WithDeadline(parent, time.Now().Add(timeout)
,設定執行當下的時間往後多久為超時。
WithValue(parent Context, key, val interface{}) Context
parent Context為第一參數,第二參數為key,第三個參數val為key對應的值。使用此函式可把資料放入context在goroutine中傳遞。
下面建立三個Context階層依序為ctx -> ctx1 -> ctx2
。使用Context.WithValue
分別在不同的Context放入值,可看到最末階的ctx2
可取得parnet Context塞入的值。此外也可看到parent Context若取消其derived Context也會被關閉。
package main
import (
"context"
"fmt"
)
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"))
}
}
沒有留言:
張貼留言