在go服務器中,對於每一個請求的request都是在單獨的goroutine中進行的,處理一個request也可能設計多個goroutine之間的交互, 使用context能夠使開發者方便的在這些goroutine裏傳遞request相關的數據、取消goroutine的signal或截止日期。golang
// A Context carries a deadline, cancelation signal, and request-scoped values // across API boundaries. Its methods are safe for simultaneous use by multiple // goroutines. type Context interface { // Done returns a channel that is closed when this Context is canceled // or times out. Done() <-chan struct{} // Err indicates why this context was canceled, after the Done channel // is closed. Err() error // Deadline returns the time when this Context will be canceled, if any. Deadline() (deadline time.Time, ok bool) // Value returns the value associated with key or nil if none. Value(key interface{}) interface{} }
Done 方法在Context被取消或超時時返回一個close的channel,close的channel能夠做爲廣播通知,告訴給context相關的函數要中止當前工做而後返回。安全
當一個父operation啓動一個goroutine用於子operation,這些子operation不可以取消父operation。下面描述的WithCancel函數提供一種方式能夠取消新建立的Context.服務器
Context能夠安全的被多個goroutine使用。開發者能夠把一個Context傳遞給任意多個goroutine而後cancel這個context的時候就可以通知到全部的goroutine。app
Err方法返回context爲何被取消。函數
Deadline返回context什麼時候會超時。測試
Value返回context相關的數據。this
// Background returns an empty Context. It is never canceled, has no deadline, // and has no values. Background is typically used in main, init, and tests, // and as the top-level Context for incoming requests. func Background() Context
BackGound是全部Context的root,不可以被cancel。spa
WithCancel返回一個繼承的Context,這個Context在父Context的Done被關閉時關閉本身的Done通道,或者在本身被Cancel的時候關閉本身的Done。
WithCancel同時還返回一個取消函數cancel,這個cancel用於取消當前的Context。設計
package main import ( "context" "log" "os" "time" ) var logg *log.Logger func someHandler() { ctx, cancel := context.WithCancel(context.Background()) go doStuff(ctx) //10秒後取消doStuff time.Sleep(10 * time.Second) cancel() } //每1秒work一下,同時會判斷ctx是否被取消了,若是是就退出 func doStuff(ctx context.Context) { for { time.Sleep(1 * time.Second) select { case <-ctx.Done(): logg.Printf("done") return default: logg.Printf("work") } } } func main() { logg = log.New(os.Stdout, "", log.Ltime) someHandler() logg.Printf("down") }
返回:code
E:\wdy\goproject>go run context_learn.go 15:06:44 work 15:06:45 work 15:06:46 work 15:06:47 work 15:06:48 work 15:06:49 work 15:06:50 work 15:06:51 work 15:06:52 work 15:06:53 down
WithTimeout func(parent Context, timeout time.Duration) (Context, CancelFunc)
WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
WithTimeout 等價於 WithDeadline(parent, time.Now().Add(timeout)).
對上面的樣例代碼進行修改
func timeoutHandler() { // ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) // go doTimeOutStuff(ctx) go doStuff(ctx) time.Sleep(10 * time.Second) cancel() } func main() { logg = log.New(os.Stdout, "", log.Ltime) timeoutHandler() logg.Printf("end") }
返回:
15:59:22 work 15:59:24 work 15:59:25 work 15:59:26 work 15:59:27 done 15:59:31 end
能夠看到doStuff在context超時的時候被取消了,ctx.Done()被關閉。
將context.WithDeadline替換爲context.WithTimeout
func timeoutHandler() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) // ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) // go doTimeOutStuff(ctx) go doStuff(ctx) time.Sleep(10 * time.Second) cancel() }
16:02:47 work 16:02:49 work 16:02:50 work 16:02:51 work 16:02:52 done 16:02:56 end
doTimeOutStuff替換doStuff
func doTimeOutStuff(ctx context.Context) { for { time.Sleep(1 * time.Second) if deadline, ok := ctx.Deadline(); ok { //設置了deadl logg.Printf("deadline set") if time.Now().After(deadline) { logg.Printf(ctx.Err().Error()) return } } select { case <-ctx.Done(): logg.Printf("done") return default: logg.Printf("work") } } } func timeoutHandler() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) // ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) go doTimeOutStuff(ctx) // go doStuff(ctx) time.Sleep(10 * time.Second) cancel() }
16:03:55 deadline set 16:03:55 work 16:03:56 deadline set 16:03:56 work 16:03:57 deadline set 16:03:57 work 16:03:58 deadline set 16:03:58 work 16:03:59 deadline set 16:03:59 context deadline exceeded 16:04:04 end
WithTimeout
package main import ( "math/rand" "time" "sync" "fmt" "context" ) func main() { rand.Seed(time.Now().Unix()) ctx,_:=context.WithTimeout(context.Background(),time.Second*3) var wg sync.WaitGroup wg.Add(1) go GenUsers(ctx,&wg) wg.Wait() fmt.Println("生成幸運用戶成功") } func GenUsers(ctx context.Context,wg *sync.WaitGroup) { //生成用戶ID fmt.Println("開始生成幸運用戶") users:=make([]int,0) guser:for{ select{ case <- ctx.Done(): //表明父context發起 取消操做 fmt.Println(users) wg.Done() break guser return default: users=append(users,getUserID(1000,100000)) } } } func getUserID(min int ,max int) int { return rand.Intn(max-min)+min }
context deadline exceeded就是ctx超時的時候ctx.Err的錯誤消息。
完整代碼參見官方文檔Go Concurrency Patterns: Context,其中關鍵的地方在於函數httpDo
func httpDo(ctx context.Context, req *http.Request, f func(*http.Response, error) error) error { // Run the HTTP request in a goroutine and pass the response to f. tr := &http.Transport{} client := &http.Client{Transport: tr} c := make(chan error, 1) go func() { c <- f(client.Do(req)) }() select { case <-ctx.Done(): tr.CancelRequest(req) <-c // Wait for f to return. return ctx.Err() case err := <-c: return err } }
httpDo關鍵的地方在於
select { case <-ctx.Done(): tr.CancelRequest(req) <-c // Wait for f to return. return ctx.Err() case err := <-c: return err }
要麼ctx被取消,要麼request請求出錯。
httpserver中實現超時
package main import ( "net/http" "context" "time" ) func CountData(c chan string) chan string { time.Sleep(time.Second*5) c<- "統計結果" return c } type IndexHandler struct {} func(this *IndexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.URL.Query().Get("count")==""{ w.Write([]byte("這是首頁")) }else { ctx,cancel:=context.WithTimeout(r.Context(),time.Second*3) defer cancel() c:=make(chan string) go CountData(c) select { case <-ctx.Done(): w.Write([]byte("超時")) case ret:=<-c: w.Write([]byte(ret)) } } } func main() { mux:=http.NewServeMux() mux.Handle("/",new(IndexHandler)) http.ListenAndServe(":8082",mux) }
func WithValue(parent Context, key interface{}, val interface{}) Context
// NewContext returns a new Context carrying userIP. func NewContext(ctx context.Context, userIP net.IP) context.Context { return context.WithValue(ctx, userIPKey, userIP) } // FromContext extracts the user IP address from ctx, if present. func FromContext(ctx context.Context) (net.IP, bool) { // ctx.Value returns nil if ctx has no value for the key; // the net.IP type assertion returns ok=false for nil. userIP, ok := ctx.Value(userIPKey).(net.IP) return userIP, ok }