Go 并发控制context实现原理剖析(小结)

2020-01-28 13:31:00王冬梅

主协程中创建一个10s超时的context,并将其传递给子协程,10s自动关闭context。程序输出如下:


HandelRequest running
WriteRedis running
WriteDatabase running
HandelRequest running
WriteRedis running
WriteDatabase running
HandelRequest running
WriteRedis running
WriteDatabase running
HandelRequest Done.
WriteDatabase Done.
WriteRedis Done.

2.5 valueCtx

源码包中src/context/context.go:valueCtx 定义了该类型context:


type valueCtx struct {
	Context
	key, val interface{}
}

valueCtx只是在Context基础上增加了一个key-value对,用于在各级协程间传递一些数据。

由于valueCtx既不需要cancel,也不需要deadline,那么只需要实现Value()接口即可。

2.5.1 Value()接口实现

由valueCtx数据结构定义可见,valueCtx.key和valueCtx.val分别代表其key和value值。 实现也很简单:


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

这里有个细节需要关注一下,即当前context查找不到key时,会向父节点查找,如果查询不到则最终返回interface{}。也就是说,可以通过子context查询到父的value值。

2.5.2 WithValue()方法实现

WithValue()实现也是非常的简单, 伪代码如下:


func WithValue(parent Context, key, val interface{}) Context {
	if key == nil {
		panic("nil key")
	}
	return &valueCtx{parent, key, val}
}

2.5.3 典型使用案例

下面示例程序展示valueCtx的用法:


package main

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

func HandelRequest(ctx context.Context) {
  for {
    select {
    case <-ctx.Done():
      fmt.Println("HandelRequest Done.")
      return
    default:
      fmt.Println("HandelRequest running, parameter: ", ctx.Value("parameter"))
      time.Sleep(2 * time.Second)
    }
  }
}

func main() {
  ctx := context.WithValue(context.Background(), "parameter", "1")
  go HandelRequest(ctx)

  time.Sleep(10 * time.Second)
}

上例main()中通过WithValue()方法获得一个context,需要指定一个父context、key和value。然后通将该context传递给子协程HandelRequest,子协程可以读取到context的key-value。

注意:本例中子协程无法自动结束,因为context是不支持cancle的,也就是说<-ctx.Done()永远无法返回。如果需要返回,需要在创建context时指定一个可以cancel的context作为父节点,使用父节点的cancel()在适当的时机结束整个context。

总结

Context仅仅是一个接口定义,跟据实现的不同,可以衍生出不同的context类型;

cancelCtx实现了Context接口,通过WithCancel()创建cancelCtx实例;