Go語言中的Goroutine是go語言中的最重要的一部分,是一個用戶級的線程是Go語言實現高併發高性能的重要緣由。可是如何中止一個已經開啓的Goroutine呢?通常有幾種方法:安全
context庫中,有4個關鍵方法:併發
WithCancel
返回一個cancel函數,調用這個函數則能夠主動中止goroutine。WithValue
WithValue能夠設置一個key/value的鍵值對,能夠在下游任何一個嵌套的context中經過key獲取value。可是不建議使用這種來作goroutine之間的通訊。WithTimeout
函數能夠設置一個time.Duration,到了這個時間則會cancel這個context。WithDeadline
WithDeadline函數跟WithTimeout很相近,只是WithDeadline設置的是一個時間點。package main import ( "context" "fmt" "time" ) func main() { //cancel ctx, cancel := context.WithCancel(context.Background()) go work(ctx, "work1") time.Sleep(time.Second * 3) cancel() time.Sleep(time.Second * 1) // with value ctx1, valueCancel := context.WithCancel(context.Background()) valueCtx := context.WithValue(ctx1, "key", "test value context") go workWithValue(valueCtx, "value work", "key") time.Sleep(time.Second * 3) valueCancel() // timeout ctx2, timeCancel := context.WithTimeout(context.Background(), time.Second*3) go work(ctx2, "time cancel") time.Sleep(time.Second * 5) timeCancel() // deadline ctx3, deadlineCancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second*3)) go work(ctx3, "deadline cancel") time.Sleep(time.Second * 5) deadlineCancel() time.Sleep(time.Second * 3) } func workWithValue(ctx context.Context, name string, key string) { for { select { case <-ctx.Done(): fmt.Println(ctx.Value(key)) println(name, " get message to quit") return default: println(name, " is running", time.Now().String()) time.Sleep(time.Second) } } } func work(ctx context.Context, name string) { for { select { case <-ctx.Done(): println(name, " get message to quit") return default: println(name, " is running", time.Now().String()) time.Sleep(time.Second) } } }
context的原理其實就是利用了channel struct{}的特性,使用select獲取channel數據。一旦關閉這個channel則會收到數據退出goroutine中的邏輯。context也是支持嵌套使用,結構就以下圖顯示利用的是一個map類型來存儲子context。關閉一個節點就會循環關閉這個節點下面的全部子節點,就實現了優雅的退出goroutine的功能。下面咱們看具體接口對象和源碼邏輯。框架
Context
接口和核心對象context interface 有4個方法函數
type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} }
emptyCtx
在上面的例子中咱們能夠看到函數context.Background(), 這個函數返回的就是一個emptyCtx
emptyCtx常常被用做在跟節點或者說是最上層的context,由於context是能夠嵌套的。在上面的Withvalue的例子中已經看到,先用emptyCtx建立一個context,而後再使用withValue把以前建立的context傳入。這個操做會在下面的分析中詳細瞭解的。
下面就是emptyCtx,其實實現很簡單全部的方法幾乎返回的都是nil。
ToDo函數返回的也是高併發
var ( background = new(emptyCtx) todo = new(emptyCtx) ) type emptyCtx int func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { return } func (*emptyCtx) Done() <-chan struct{} { return nil } func (*emptyCtx) Err() error { return nil } func (*emptyCtx) Value(key interface{}) interface{} { return nil } func (e *emptyCtx) String() string { switch e { case background: return "context.Background" case todo: return "context.TODO" } return "unknown empty Context" } var ( background = new(emptyCtx) todo = new(emptyCtx) ) func Background() Context { return background } func TODO() Context { return todo }
cancelCtx
cancelCtx是context實現裏最重要的一環,context的取消幾乎都是使用了這個對象。WithDeadline WithTimeout其實最終都是調用的cancel的cancel函數來實現的。
對象中的字段:源碼分析
cancel主要函數:性能
Done
Done函數返回一個chan struct{}的channel,用來判斷context是否已經被close了。從上面的例子能夠看到使用一個select 來判斷context是否被關閉。一旦從外部調用cancel函數關閉了context的done屬性,select則能夠拿到輸出,最終關閉這個context測試
Cancel
Cancel函數用來在外部調用,調用以後主要操做:ui
type cancelCtx struct { Context mu sync.Mutex // protects following fields done chan struct{} // created lazily, closed by first cancel call children map[canceler]struct{} // set to nil by the first cancel call err error // set to non-nil by the first cancel call } // 能夠被cancel的對象,實現者是*cancelCtx 和 *timerCtx. type canceler interface { cancel(removeFromParent bool, err error) Done() <-chan struct{} } func (c *cancelCtx) Done() <-chan struct{} { c.mu.Lock() if c.done == nil { c.done = make(chan struct{}) } d := c.done c.mu.Unlock() return d } func (c *cancelCtx) Err() error { c.mu.Lock() defer c.mu.Unlock() return c.err } func (c *cancelCtx) String() string { return fmt.Sprintf("%v.WithCancel", c.Context) } // cancel closes c.done, cancels each of c's children, and, if // removeFromParent is true, removes c from its parent's children. func (c *cancelCtx) C(removeFromParent bool, err error) { if err == nil { panic("context: internal error: missing cancel error") } c.mu.Lock() if c.err != nil { c.mu.Unlock() return // already canceled } c.err = err if c.done == nil { c.done = closedchan } else { close(c.done) } for child := range c.children { // NOTE: acquiring the child's lock while holding parent's lock. child.cancel(false, err) } c.children = nil c.mu.Unlock() if removeFromParent { removeChild(c.Context, c) } }
timerCtx
timeCtx實際上是在cancelCtx基礎上增長timer屬性。其中的cancel函數也是調用cancelCtx的Cancel函數。this
type timerCtx struct { cancelCtx timer *time.Timer // Under cancelCtx.mu. deadline time.Time } func (c *timerCtx) cancel(removeFromParent bool, err error) { c.cancelCtx.cancel(false, err) if removeFromParent { // Remove this timerCtx from its parent cancelCtx's children. removeChild(c.cancelCtx.Context, c) } c.mu.Lock() if c.timer != nil { c.timer.Stop() c.timer = nil } c.mu.Unlock() } func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { return c.deadline, true }
WithCancel
WithDeadline
WithTimeout
WithValue
這三個方法是對於context使用的一個封裝,在最上邊的例子裏咱們能夠看到是如何使用的。在這段咱們是要看的是如何實現的源碼。
WithCancel
WithCancel函數返回context和一個主動取消的函數,外部只要調用這個函數則會close context中channel。
返回的函數測試cancelCtx中測cancel函數,在上面已經有了詳細說明這裏就不過多描述了。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { c := newCancelCtx(parent) propagateCancel(parent, &c) return &c, func() { c.cancel(true, Canceled) } }
WithDeadline
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { if cur, ok := parent.Deadline(); ok && cur.Before(d) { // The current deadline is already sooner than the new one. return WithCancel(parent) } c := &timerCtx{ cancelCtx: newCancelCtx(parent), deadline: d, } propagateCancel(parent, c) dur := time.Until(d) if dur <= 0 { c.cancel(true, DeadlineExceeded) // deadline has already passed return c, func() { c.cancel(true, Canceled) } } c.mu.Lock() defer c.mu.Unlock() if c.err == nil { c.timer = time.AfterFunc(dur, func() { c.cancel(true, DeadlineExceeded) }) } return c, func() { c.cancel(true, Canceled) } }
WithTimeout
WithTimeout實現很簡單,其實就是調用了WithDeadline方法,傳入已經計算過的deadline。
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { return WithDeadline(parent, time.Now().Add(timeout)) }
WithValue
WithValue 不返回cancel函數,只是把傳入的key和value保存起來。方便上下游節點根據key獲取value。
type valueCtx struct { Context key, val interface{} } func (c *valueCtx) String() string { return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val) } func (c *valueCtx) Value(key interface{}) interface{} { if c.key == key { return c.val } return c.Context.Value(key) } func WithValue(parent Context, key, val interface{}) Context { if key == nil { panic("nil key") } if !reflect.TypeOf(key).Comparable() { panic("key is not comparable") } return &valueCtx{parent, key, val} }
從網上看到了一些使用原則,把他摘抄下來:
上面講述了context的用法和源碼,其實有不少框架都實現了本身的context。其實只要繼承了context接口就是一個context對象。Context是你們都比較推薦的一種中止goroutine的一種方式,而且context支持嵌套,中止跟節點它下面全部的子節點都會中止。