go库源码学习分析(二)-context

254 阅读2分钟

上一篇分析链接池的时候,看到了一个ctx, cancel := context.WithCancel(context.Background()) 这个操作,这个是使用了context库,分析下这个库的作用和源码

  1. context是用来做什么了 我们的main主协程启动了一个go协程,由于这个协程运行了太久的时候,我们想取消这个启动的协程,可以使用channel的方式,还可以使用的就是这个context方法,context就是记录当前的上下文

  2. context的使用

//手动取消
func main()  {
	//获取上下文
	ctx,cancel := context.WithCancel(context.Background())
	//给上下文传递添加的值
	withValue :=context.WithValue(ctx,"xiaobai","add value")
	go watch(withValue)
	time.Sleep(10*time.Second)
	//发送取消事件
	cancel()

	time.Sleep(3*time.Second)
}

func watch(ctx context.Context)  {
	for {
		select {
		case <-ctx.Done():
			fmt.Println(ctx.Value("xiaobai"),"is cancel")
			return
		default:
			fmt.Println(ctx.Value("xiaobai"),"is goruntine")
			time.Sleep(2*time.Second)
		}
	}
}

  1. 为什么执行了cancel函数,就会自动取消go协程,下面看看context的源码
type Context interface {
    //记录运行的时间	
	Deadline() (deadline time.Time, ok bool)
    //当停止的时候 发送channel
	Done() <-chan struct{}
    //错误信息
	Err() error
    //传递一个interface变量
	Value(key interface{}) interface{}
}
  1. go的context包实现了上面的方法,接下来看看context的使用
//使用
context.WithCancel(context.Background())

//在这里返回了一个c.canel的函数,外部使用这个cancel函数就可以发送关闭请求
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	//创建一个新的链接
	c := newCancelCtx(parent)
	propagateCancel(parent, &c)
	return &c, func() { c.cancel(true, Canceled) }
}

//创建一个对象
func newCancelCtx(parent Context) cancelCtx {
	return cancelCtx{Context: parent}
}

func propagateCancel(parent Context, child canceler) {
	
	if parent.Done() == nil {
		return // parent is never canceled
	}
	//如果是存在的链接
	if p, ok := parentCancelCtx(parent); ok {
		p.mu.Lock()
		//如果存在错误,关闭连接
		if p.err != nil {
			// parent has already been canceled
			child.cancel(false, p.err)
		} else {
		    //如果不存在 创建并且保存map
			if p.children == nil {
				p.children = make(map[canceler]struct{})
			}
			p.children[child] = struct{}{}
		}
		p.mu.Unlock()
	} else {
	    //监听
		go func() {
			select {
			case <-parent.Done():
				child.cancel(false, parent.Err())
			case <-child.Done():
			}
		}()
	}
}

//外部调用这个函数,发送关闭请求
func (c *cancelCtx) cancel(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
	//如果c.done是空的话,复制closechan
	if c.done == nil {
		c.done = closedchan
	} else {
	    //关闭了c.done
		close(c.done)
	}
	//循环删除所有的children
	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)
	}
}

通过上面的代码,当我们在外部执行了cancel的时候,会close(d.done),当收到c.done的请求的时候,就可以关闭当前的协程

其他的几种方式,其实源码也是一样的

//超时自动截至
var wg sync.WaitGroup
func main()  {
	ctx ,cancel := context.WithTimeout(context.Background(),time.Second*4)
	defer cancel()
	fmt.Println("I am goint to work")
	times,ok := ctx.Deadline()
	fmt.Println("time",times)
	fmt.Println("ok",ok)
	//未获取到异常
	err := ctx.Err()
	fmt.Println(err)
	wg.Add(1)
	go worker(ctx)
	wg.Wait()

	times,ok = ctx.Deadline()
	fmt.Println("time",times)
	fmt.Println("ok",ok)
	//获取到异常
	err = ctx.Err()
	fmt.Println(err)
	fmt.Println("finshed goint to work")
}
func worker(ctx context.Context) error  {
	defer wg.Done()
	for i:=0;i<1000;i++ {
		select {
		case <-time.After(time.Second*1):
			fmt.Println("运行了多少次",i)
			fmt.Println("dong some work")
		case <-ctx.Done():
			fmt.Println("Cancel the content",i)
			//结束运行 抛出一个错误
			return ctx.Err()
		}
	}
	return nil
}


//截至时间 自动停止
func main() {
	times := time.Now().Add(time.Second * 10)
	ctx, cancel := context.WithDeadline(context.Background(), times)
	defer cancel()
	for {
		select {
		case <-time.After(2*time.Second):
			fmt.Println("oversleep")
		case <-ctx.Done():
			fmt.Println(ctx.Err())
			return
		}
	}

}

参考