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支持嵌套,中止跟节点它下面全部的子节点都会中止。