【Go】源码学习:context包

一、概述

context包是在go1.7版本中从golang.org/x/net/context包挪入到标准库中。从标准库的GOROOT/src/context包下我们研究分析源码可以看到其底层实现是基于一个Context interface及其实现类(strcut)来实现:

  • 对一个无缓冲channel的关闭,使 <-context.Done()可执行通过。
  • 使用map实现对子孙context进行包裹,便后续可拿到child(子孙context)进行cancel()操作。
  • 对执行完cancel()的context将可将其从父context children map移除。
  • 到达指定截止日期时间后取消context
  • key - value 键值对存储数据的context,实现子父context间的数据传递作用。

通过以上的context功能特点context可以运用于多个goroutine之间的进程管理,信息通知,和数据传递的程序中使用。

二、源码分析

1、Context interface 的定义

context包基于一个Context接口抽象出4种行为方法,其子类实现具体的方法即可实现各自的功能特点

type Context interface {
		Deadline() (deadline time.Time, ok bool)
		Done() <-chan struct{}
		Err() error
		Value(key interface{}) interface{}
}

Deadline(): 截止日期,该方法若没设置deadline 一个具体时间,则ok为false
Done() : 用于生成一个只读的channel
Err() : 当该上述channel关闭时给定一个err错误的理由提示,没关闭err为nil
Value() : 通过key获取 value的一个方法

2、canceler interface 的定义

canceler接口里定义了一个cancel()方法用于关闭channel管道和从父context children map移除子context。Done()方法用于返回一个只读的channel.

type canceler interface {
	cancel(removeFromParent bool, err error)
	Done() <-chan struct{}
}

实现了该接口的context上下文可以直接进行取消动作。

3 emptyCtx : 为一个空的上下文,不会被取消,没有值,没有截止日期。作为一个背景,即所有context上下文的祖先
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)
)

可以看到上述emptyCtx是一个自定义类型,对Context里的方法进行了一个空实现,目的为下文的background和toto对象做一个实列,提供一个顶层上下文(top-level context),即祖先context

var (
	background = new(emptyCtx)
	todo       = new(emptyCtx)
)
func Background() Context {
	return background
}
func TODO() Context {
	return todo
}

此处通过内建new()方法实列两个指向emptyCtx的指针对象,通过Background()和TODO()方法向外提供其引用。background一般常用做顶层context,初始化,测试。toto一般当不清楚要使用哪个上下文或者它还不可用时使用。

4 cancelCtx :用于关闭channel实现取消作用的context。其继承Context而且实现了canceler接口
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
}
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()
	err := c.err
	c.mu.Unlock()
	return err
}
  1. cancelCtx 继承Context接口,并实现了canceler接口里的cancel()方法。只要实现了该cancelCtx的结构体当其取消时其子child context也同样会被取消。
  2. cancelCtx里使用了Mutex 锁机制对其成员字段做了线程安全的操作。
  3. 其Done()方法用于初始化一个channel(无缓冲)
  4. Err()方法用于channel关闭后即cancel后的一个理由通知初始化,未cancel则err为nil
  5. children为一map结构,用于存储其子context。用于后面其取消时进行遍历取消子context
/**
此方法用于实例一个带取消的cancelCtx,返回cancelCtx的实例和一个取消函数
 */
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	c := newCancelCtx(parent)
	propagateCancel(parent, &c)
	return &c, func() { c.cancel(true, Canceled) }
}

// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {
	return cancelCtx{Context: parent}
}

// 判断父类是否已被取消,若取消(err!=nil)则其子context调用取消,否则将其加入到父context children map里
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 {
			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():
			}
		}()
	}
}

//不断需找其最近的一个父context是否是cancelCtx类型,是返回则true 否则 false
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
	for {
		switch c := parent.(type) {
		case *cancelCtx:
			return c, true
		case *timerCtx:
			return &c.cancelCtx, true
		case *valueCtx:
			parent = c.Context
		default:
			return nil, false
		}
	}
}

//从父context children map里移除子context
func removeChild(parent Context, child canceler) {
	p, ok := parentCancelCtx(parent)
	if !ok {
		return
	}
	p.mu.Lock()
	if p.children != nil {
		delete(p.children, child)
	}
	p.mu.Unlock()
}

万剑归宗:cancel() 关闭channel,使<-channel执行通过,不阻塞(ps:取消功能)
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  该context早已取消
	}
	c.err = err
	if c.done == nil {
		c.done = closedchan 
	} else {
		close(c.done)  //关闭channel通道,此时对<-ctx.Done()则执行通过(context原理)
	}
	//遍历父 children里的context,对应一个一个的cancel
	for child := range c.children {
		child.cancel(false, err)
	}
	c.children = nil
	c.mu.Unlock()

	if removeFromParent {
		removeChild(c.Context, c) //从父children map移除自己
	}
}
5 timerCtx : 一个到达指定时间自动进行关闭channel实现取消作用的context
type timerCtx struct {
	cancelCtx
	timer *time.Timer // Under cancelCtx.mu. 此对象受其父成员锁的保护

	deadline time.Time
}

func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
	return c.deadline, true
}

func (c *timerCtx) String() string {
	return contextName(c.cancelCtx.Context) + ".WithDeadline(" +
		c.deadline.String() + " [" +
		time.Until(c.deadline).String() + "])"
}

timerCtx 继承里cancelCtx接口拥有其方法行为,且追加一个deadline成员变量用于标记该context到达该时间后自动取消context(ps:close channel, <-channel pass)

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
	//若其父context设置了到期截止日期,且其父context的截止日期早已本context的截止日期,则直接WithCancel()取消
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
		// The current deadline is already sooner than the new one.
		return WithCancel(parent)
	}
	//init timerCtx
	c := &timerCtx{
		cancelCtx: newCancelCtx(parent),
		deadline:  d,
	}
	//判断父context是否取消:
	//若取消则取消本context
	//若没取消则将本context加入到父context的children map里管理
	propagateCancel(parent, c)
	dur := time.Until(d)
	if dur <= 0 { //当前时间已经超过了截止日期,进行cancel操作
		c.cancel(true, DeadlineExceeded) // deadline has already passed
		return c, func() { c.cancel(false, Canceled) }
	}
	c.mu.Lock()
	defer c.mu.Unlock()
	if c.err == nil {
	//timerCtx灵魂所在:使用AfterFunc()方法实现到期后自动执行cancel()方法进行取消
		c.timer = time.AfterFunc(dur, func() {
			c.cancel(true, DeadlineExceeded)
		})
	}
	return c, func() { c.cancel(true, Canceled) } //返回实例的本contxet,和func可直接调用此func立即取消本contet
}

通过对WithDeadline()方法的仔细分析可以看到timerCtx是通过time.AfterFunc()函数实现对context的到期自动调用func(){c.cancel(bool,error)}方法实现取消功能。

func (c *timerCtx) cancel(removeFromParent bool, err error) {
	c.cancelCtx.cancel(false, err) //调用父cancel()方法实现真正的取消
	if removeFromParent {
		// Remove this timerCtx from its parent cancelCtx's children.
		removeChild(c.cancelCtx.Context, c) //从父context移除本context
	}
	c.mu.Lock()
	if c.timer != nil {
		c.timer.Stop() //stop timer计时器
		c.timer = nil
	}
	c.mu.Unlock()
}

cancel()方法对其父cancel()方法进行重写来实现对相关资源的释放,但内部还是通过其父cancel()来实现取消功能

5 valueCtx : 使用key - value键值形式来对数据进行context之间的传递,没有cancel()取消的作用。
type valueCtx struct {
	Context
	key, val interface{}
}

func (c *valueCtx) Value(key interface{}) interface{} {
	if c.key == key {
		return c.val
	}
	return c.Context.Value(key)
}

分析可得,valueCtx 采用key-value的形式来存储数据。基础了Context接口,重写了Value()方法。若设置的key在本context找不到则从其父级获取,逐级推进。实现了context链之间的数据传递和共享。此上下文没有取消的功能。故一般常和其他context组合使用。如cancelCtx,timerCtx.

三、使用示列

  • WithCancel(ctx) 直接取消本context (close channel and remove context from parent context)
cancelCtx,cancelFunc := context.WithCancel(context.Background())
cancelFunc () 调用取消
  • WithDeadline(ctx,d) 到达d时间后自动取消本context
deadlineCtx,_ := context.WithDeadline(context.Background(),time.Now().Add(5*time.Second)) 
  • WithTimeout(ctx,t) 从当前开始的 t 时间后自动取消本context。其本质就是对WithDeadline()的一个小小封装
timeCtx,_:=context.WithTimeout(context.Background(),2*time.Second) //2s后取消
  • WithValue(ctx,k,v) context之间存储key-value数据,通过key-拿到value
ctx := context.WithValue(context.Background(),1,2)
fmt.Println(ctx.Value(1))  // 2

detail code:

package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

/**
多个goruntine的并发控制实现
 */
func main(){
	//func1()
	//func2()
	//func3()
	//func4()

	//withCancelTest()
	//withDeadlineTest()
	//withTimeTest()
	withValueTest()
}

//channel + select + sleep 无缓存实现goruntine关闭控制
func func1(){
	ch1:=make(chan bool)
	ch2:=make(chan bool)
	go Worker1(ch1)
	go Worker2(ch2)
	time.Sleep(3*1e9)
	ch1<-true
	time.Sleep(3*1e9)
	ch2<-true
	fmt.Println("main ending")
}

func Worker1(ch chan bool){
	for{
		select{
		case <-ch:{
			fmt.Println("Worker1: I was cancled by parent goruntinue")
			return
		}
		default:
			time.Sleep(1e9)
			fmt.Println("Worker1: I am doing my work")
		}
	}
}
func Worker2(ch chan bool){
	for{
		select{
		case <-ch:{
			fmt.Println("Worker2: I was cancled by parent goruntinue")
			return
		}
		default:
			time.Sleep(1e9)
			fmt.Println("Worker2: I am doing my work")
		}
	}
}

//channel + sleep 实现goruntine控制,此方法存在goruntine执行的不确定性
func func2(){
	ch:=make(chan int)
	go func()(){
		for{
			fmt.Println("Goroutine1:",<-ch)
		}
	}()
	go func()(){
		for{
			fmt.Println("Goroutine2:",<-ch)
		}
	}()
	ch<-1
	ch<-1
	time.Sleep(5*1e9)
	fmt.Println("main goroutine ending!")
}

//Mutex加锁 + sleep 实现数据原子操作
type request struct{
	message string
	m sync.Mutex
}
func func3(){
	req:=new(request)
	req.message="helloworld"

	go func(req *request)(){
		req.m.Lock()
		fmt.Println(req.message)
		req.m.Unlock()
	}(req)
	go func(req *request)(){
		req.m.Lock()
		req.message="It's changed!"
		req.m.Unlock()
	}(req)
	time.Sleep(3*1e9)
	fmt.Println("main goroutine ending!")
}
//wg实现对平级的多个goruntine的管理控制
func func4(){
	var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		fmt.Println("Routine1 is done")
		wg.Done()
	}()
	go func() {
		fmt.Println("Routine2 is done")
		wg.Done()
	}()
	wg.Wait()//阻塞于此
	fmt.Println("main goroutine ending!")
}
/**
context实现对复杂goroutine树的管理,即对goroutine的终结.
WithCancel本质:通过关闭channel来执行<-ctx.Done(),实现子goroutine执行结束。调用返回的方法cancelFunc关闭通道结束子goroutine.
 */
func withCancelTest(){
	bg := context.Background()
	cancelCtx,cancelFunc := context.WithCancel(bg)
	go func(ctx context.Context) {
		for {
			select {
				case <-ctx.Done(): //创建一个无缓存的只读channel,阻塞于此(canceler接口方法)
					fmt.Println("child goroutine 1 work end! err:",ctx.Err())//关闭chann需要一个err理由
					return
			default:
				time.Sleep(time.Second)
				fmt.Println("child goroutine 1 working...")
			}
		}
	}(cancelCtx)
	go func(ctx context.Context) {
		for {
			select {
			case <-ctx.Done(): //创建一个无缓存的只读channel,阻塞于此(canceler接口方法)
				fmt.Println("child goroutine 2 work end! err:",ctx.Err())//关闭chann需要一个err理由
				return
			default:
				time.Sleep(time.Second)
				fmt.Println("child goroutine 2 working...")
				cancelFunc()
			}
		}
	}(cancelCtx)
	time.Sleep(5*time.Second)
	//调用方法,关闭channel通道,<-ctx.Done()才执行
	//cancelFunc()
	fmt.Println("main goroutine end")
}
/**
WithDeadline:到达(超过)指定时间后该context和其子孙context都将被取消,底层还是基于WithCancel,只是加了一个过期时间判断
 */
func withDeadlineTest(){
	deadlineCtx,_ := context.WithDeadline(context.Background(),time.Now().Add(2*time.Second))//2s后(channel关闭,<-ctxDone()执行)结束goroutine
	go func(ctx context.Context) {
		fmt.Println("chile goroutine work...")
		for{
			select {
			case <-ctx.Done():
				fmt.Println("child goroutine work end! err: ",ctx.Err())
				return
			default:
				time.Sleep(1 * time.Second)
				fmt.Println("goroutine working...")
			}
		}
	}(deadlineCtx)
	fmt.Println("main goroutine work...")
	time.Sleep(3 * time.Second)
	fmt.Println("main goroutine end")
}
/**
WithTimeout:本质是对WithDeadline()方法的封装.功能为在当前时间的timeout时间后关闭channel,结束goroutine。
WithDeadline(parent, time.Now().Add(timeout))
 */
func withTimeTest(){
	timeCtx,_:=context.WithTimeout(context.Background(),2*time.Second)//2s后停止goroutine
	go func(ctx context.Context) {
		for{
			select {
			case <-ctx.Done():
				fmt.Println("goroutine work end。 err: ",ctx.Err())
				return
			default:
				time.Sleep(1 * time.Second)
				fmt.Println("goroutine working...")
			}
		}
	}(timeCtx)
	fmt.Println("main goroutine work...")
	time.Sleep(3 * time.Second)
	fmt.Println("main goroutine end")
}
/**
WithValue:用于存储一个key-value值的context对象,此对象可以作为withCancel函数的第一个参数传入使用。达到多个goroutine之间的数据使用作用
   		  配合withCancel函数实现关闭channel,结束goroutine。
 */
func withValueTest() {
	ctx := context.WithValue(context.Background(),1,2)
	fmt.Println(ctx.Value(1))
}

你可能感兴趣的:(Go)