參考:Golang中 Context包深入淺出
參考:golang中Context的使用場景
文章目錄
1、作用說明
一個網絡請求Request,每個Request都需要開啓一個goroutine做一些事情,這些goroutine又可能會開啓其他的goroutine。
所以我們需要一種可以跟蹤goroutine的方案,纔可以達到控制他們的目的,這就是Go語言爲我們提供的Context,稱之爲上下文非常貼切,它就是goroutine的上下文。
也就是說面對多個gogroutine可能又開啓子gogroutine的情況,我們可以通過context包來控制這些協程的聲明週期
2、方法說明
1、Context接口
1、Dealine
獲取設置的截止時間,deadline是截止時間,到了這個時間點,Context會自動發起取消請求;
返回值ok==false時表示沒有設置截止時間,如果需要取消的話,需要調用取消函數進行取消。
// Deadline returns the time when work done on behalf of this context
// should be canceled. Deadline returns ok==false when no deadline is
// set. Successive calls to Deadline return the same results.
Deadline() (deadline time.Time, ok bool)
2、Done
返回一個只讀的chan,類型爲struct{}。
我們在goroutine中,如果該方法返回的chan可以讀取,則意味着parent context已經發起了取消請求,否則返回nil
// Done returns a channel that's closed when work done on behalf of this
// context should be canceled. Done may return nil if this context can
// never be canceled. Successive calls to Done return the same value.
// Done is provided for use in select statements:
Done() <-chan struct{}
3、Err
如果還沒有被關閉,返回nil。否則返回取消的錯誤原因。
// If Done is not yet closed, Err returns nil.
// If Done is closed, Err returns a non-nil error explaining why:
// Canceled if the context was canceled
// or DeadlineExceeded if the context's deadline passed.
// After Err returns a non-nil error, successive calls to Err return the same error.
Err() error
4、Value
返回Context上綁定的數據,通過key來獲取對應的value
Value(key interface{}) interface{}
2、Background
主要用於main函數、初始化以及測試代碼中,作爲Context這個樹結構的最頂層的Context,也就是根Contex
// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming
// requests.
func Background() Context
3、TODO
不確定使用場景和還沒需要用到Context入參的時候使用
// TODO returns a non-nil, empty Context. Code should use context.TODO when
// it's unclear which Context to use or it is not yet available (because the
// surrounding function has not yet been extended to accept a Context
// parameter).
func TODO() Context
4、WithCancel
返回一個子Context和一個CanceFunc函數,當調用CancelFunc或者父Context結束的時候,這個Context也會關閉
// WithCancel returns a copy of parent with a new Done channel. The returned
// context's Done channel is closed when the returned cancel function is called
// or when the parent context's Done channel is closed, whichever happens first.
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
5、WithDeadline
返回一個子Context。傳入的時間d爲最後截至時間,然後父Context如果結束的話,還沒到截止時間,子Context也會結束。
// WithDeadline returns a copy of the parent context with the deadline adjusted
// to be no later than d. If the parent's deadline is already earlier than d,
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
// context's Done channel is closed when the deadline expires, when the returned
// cancel function is called, or when the parent context's Done channel is
// closed, whichever happens first.
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
6、WithTimeout
底層也是調用WithDeadline,在多少時間之後超時關閉。
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete:
//
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
// defer cancel() // releases resources if slowOperation completes before timeout elapses
// return slowOperation(ctx)
// }
func WithTimeout(parent Context, timeout time.Duration)
7、WithValue
生成一個帶key-value值得Context上下文。
// WithValue returns a copy of parent in which the value associated with key is
// val.
//
// Use context Values only for request-scoped data that transits processes and
// APIs, not for passing optional parameters to functions.
//
// The provided key must be comparable and should not be of type
// string or any other built-in type to avoid collisions between
// packages using context. Users of WithValue should define their own
// types for keys. To avoid allocating when assigning to an
// interface{}, context keys often have concrete type
// struct{}. Alternatively, exported context key variables' static
// type should be a pointer or interface.
func WithValue(parent Context, key, val interface{}) Context
3、使用示例
1、控制一組gogroutine
當多個協程運行的時候,如果有一個協成出現問題,我們希望關閉所有的協程的時候,使用Contetxt
//計算函數 - 開啓gogroutine
func Calculate(ctx context.Context, url string) error {
result := make(chan int)
err := make(chan error)
go func() {
// 進行RPC調用,並且返回是否成功,成功通過result傳遞成功信息,錯誤通過error傳遞錯誤信息
if true {
result <- 1
} else {
err <- errors.New("some error happen")
}
}()
select {
case <-ctx.Done(): // 其他調用調用失敗
return ctx.Err()
case e := <-err: // 本協同調用失敗,返回錯誤信息
return e
case <-result: // 本攜程調用成功,不返回錯誤信息
return nil
}
}
//主函數
func main() {
ctx, cancel := context.WithCancel(context.Background())
err := Rpc(ctx, "http://rpc_1_url")
if err != nil {
return
}
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
err := Rpc(ctx, "string1")
if err != nil {
cancel()
}
}()
//其他協成調用,同上
wg.Wait()
}
2、超時請求
官方例子,設置超時時間爲50毫秒,在1秒的時候會有輸出,但是50毫秒時間到了就超時了,直接報錯。
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err()) // prints "context deadline exceeded"
}
}
http請求超時的使用,http實現Context接口,直接設置超時時間。
uri := "https://httpbin.org/delay/3"
req, err := http.NewRequest("GET", uri, nil)
if err != nil {
log.Fatalf("http.NewRequest() failed with '%s'\n", err)
}
ctx, _ := context.WithTimeout(context.Background(), time.Millisecond*100)
req = req.WithContext(ctx)
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatalf("http.DefaultClient.Do() failed with:\n'%s'\n", err)
}
defer resp.Body.Close()
3、HTTP服務器的request互相傳遞數據
Context可以攜帶key-value對應的結構值,所以我們可以通過request傳輸攜帶值的Context,而在對應的服務器進行解析
type FooKey string
var UserName = FooKey("user-name")
var UserId = FooKey("user-id")
func foo(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), UserId, "1")
ctx2 := context.WithValue(ctx, UserName, "yejianfeng")
next(w, r.WithContext(ctx2))
}
}
func GetUserName(context context.Context) string {
if ret, ok := context.Value(UserName).(string); ok {
return ret
}
return ""
}
func GetUserId(context context.Context) string {
if ret, ok := context.Value(UserId).(string); ok {
return ret
}
return ""
}
func test(w http.ResponseWriter, r *http.Request) {
//獲取key對應的value
w.Write([]byte(GetUserId(r.Context())))
w.Write([]byte(GetUserName(r.Context())))
}
func main() {
http.Handle("/", foo(test))
http.ListenAndServe(":8080", nil)
}