本文主要基于官方文檔Go Concurrency Patterns: Context以及視頻Advanced Go Concurrency Patterns的學(xué)習(xí)而得。
背景
在go服務(wù)器中,對(duì)于每個(gè)請(qǐng)求的request都是在單獨(dú)的goroutine中進(jìn)行的,處理一個(gè)request也可能設(shè)計(jì)多個(gè)goroutine之間的交互, 使用context可以使開發(fā)者方便的在這些goroutine里傳遞request相關(guān)的數(shù)據(jù)、取消goroutine的signal或截止日期。
Context結(jié)構(gòu)
// 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被取消或超時(shí)時(shí)返回一個(gè)close的channel,close的channel可以作為廣播通知,告訴給context相關(guān)的函數(shù)要停止當(dāng)前工作然后返回。
當(dāng)一個(gè)父operation啟動(dòng)一個(gè)goroutine用于子operation,這些子operation不能夠取消父operation。下面描述的WithCancel函數(shù)提供一種方式可以取消新創(chuàng)建的Context.
Context可以安全的被多個(gè)goroutine使用。開發(fā)者可以把一個(gè)Context傳遞給任意多個(gè)goroutine然后cancel這個(gè)context的時(shí)候就能夠通知到所有的goroutine。
Err方法返回context為什么被取消。
Deadline返回context何時(shí)會(huì)超時(shí)。
Value返回context相關(guān)的數(shù)據(jù)。
繼承的Context
BackGround
// 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。
WithCancel
// WithCancel returns a copy of parent whose Done channel is closed as soon as
// parent.Done is closed or cancel is called.
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
WithCancel返回一個(gè)繼承的Context,這個(gè)Context在父Context的Done被關(guān)閉時(shí)關(guān)閉自己的Done通道,或者在自己被Cancel的時(shí)候關(guān)閉自己的Done。
WithCancel同時(shí)還返回一個(gè)取消函數(shù)cancel,這個(gè)cancel用于取消當(dāng)前的Context。
視頻Advanced Go Concurrency Patterns中的第一個(gè)關(guān)于WithCancel的樣例代碼,本人有所修改
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一下,同時(shí)會(huì)判斷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")
}
結(jié)果
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
withDeadline withTimeout
WithTimeout func(parent Context, timeout time.Duration) (Context, CancelFunc)
WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
WithTimeout 等價(jià)于 WithDeadline(parent, time.Now().Add(timeout)).
對(duì)上面的樣例代碼進(jìn)行修改
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超時(shí)的時(shí)候被取消了,ctx.Done()被關(guān)閉。
將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
根據(jù)視頻Advanced Go Concurrency Patterns5分48秒處的代碼編寫doTimeOutStuff替換doStuff
func doTimeOutStuff(ctx context.Context) {
for {
time.Sleep(1 * time.Second)
if deadline, ok := ctx.Deadline(); ok { //設(shè)置了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
context deadline exceeded就是ctx超時(shí)的時(shí)候ctx.Err的錯(cuò)誤消息。
搜索測(cè)試程序
完整代碼參見官方文檔Go Concurrency Patterns: Context,其中關(guān)鍵的地方在于函數(shù)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關(guān)鍵的地方在于
select {
case <-ctx.Done():
tr.CancelRequest(req)
<-c // Wait for f to return.
return ctx.Err()
case err := <-c:
return err
}
要么ctx被取消,要么request請(qǐng)求出錯(cuò)。
WithValue
func WithValue(parent Context, key interface{}, val interface{}) Context
參見搜索程序userip中的代碼
關(guān)鍵的代碼如下:
// 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
}
go doc中的信息
The WithCancel, WithDeadline, and WithTimeout functions take a Context (the
parent) and return a derived Context (the child) and a CancelFunc. Calling
the CancelFunc cancels the child and its children, removes the parent's
reference to the child, and stops any associated timers.
里面需要注意的就是 調(diào)用CancelFunc會(huì)取消child以及child生成的context,取出父context對(duì)這個(gè)child的引用,停止相關(guān)的計(jì)數(shù)器。
后記
之前一直在CSDN上寫文章,后面會(huì)逐步轉(zhuǎn)換到簡(jiǎn)書上,還請(qǐng)大家多多支持。