c++调用golang

应用场景 主要在goroutine之间传递上下文信息,包括:退出信号、超时与截止时间、k-v键值对等。 context的产生 goroutine本身并没有parent/children关系,当遇到复杂的并发结构时,处理下游goroutine的退出将会变得艰难。 比如一个网络请求Request,

应用场景

主要在goroutine之间传递上下文信息,包括:退出信号、超时与截止时间、k-v键值对等。

context的产生

goroutine本身并没有parent/children关系,当遇到复杂的并发结构时,处理下游goroutine的退出将会变得艰难。

比如一个网络请求Request,每一个请求都需要开启一个或者多个goroutine处理一些业务,这些goroutine处理的过程中又衍生了更多的goroutine,goroutine的关系链把场景变的非常复杂。

在Go1.7版本开始提供了context标准库来解决类似问题。

context底层实现

整体类图

interface

context接口

作为一个基本接口,所有的context对象都要实现该接口。内部定义了4个方法(都是幂等)。

type Context interface {   // 返回取消该上下文完成的工作的时间。   // 如果未设置截止日期,则截止日期返回ok == false。   // 连续调用Deadline会返回相同的结果。   Deadline() (deadline time.Time, ok bool)   // 在context被取消或者到了deadline,返回一个被关闭的channel。   // 在源码中,因为没有任何地方向这个channel里面写入值,而且这是一个只读   // 的channel,因此,在goroutine中监听这个channel的时候,只有被关   // 闭的时候,才会读取到相应类型的零值,后续子goroutine便可以做退出操作。   //  func Stream(ctx context.Context, out chan   //     for {   //        v, err := DoSomething(ctx)   //        if err != nil {   //           return err   //        }   //        select {   //        case    //           return ctx.Err()   //        case out    //        }   //     }   //  }   Done() struct{}   // 如果尚未关闭Done,则Err返回nil。   // 如果关闭了Done,Err将返回一个被关闭的原因:   // 1、如果context已取消,返回context canceled;   // 2、如果context的截止日期已过,返回context deadline exceeded。   Err() error   // 获取key对应的value   Value(key interface{}) interface{}}

canceler接口

为拓展接口。实现了此接口的context,说明该context是可取消的。

// A canceler is a context type that can be canceled directly. The// implementations are *cancelCtx and *timerCtx.type canceler interface {   cancel(removeFromParent bool, err error)   Done() chan }

struct

emptyCtx

emptyCtx实现了一个不具备任何功能的context接口,也就是一个空的contex。emptyCtx永远不会被cancel,它没有值,也没有 deadline。

它不是struct {},因为此类型的var必须具有不同的地址。它主要是作为context对象树的root节点。

type emptyCtx intfunc (*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}// todo和background两者本质上只有名字区别,在按string输出的时候会有区别。// emptyCtx的String方法可以返回当前context的具体类型,比如是Background还是TODO。// background和todo是两个全局变量,这里通过取其地址来进行对应类型的判断。func (e *emptyCtx) String() string {   switch e {   case background:      return "context.Background"   case todo:      return "context.TODO"   }   return "unknown empty Context"}

emptyCtx它被包装成全局变量,分别通过context.Background()和context.TODO()两个函数对用户公开。

var (   background = new(emptyCtx)   todo       = new(emptyCtx))// Background返回一个非空的Context。// 它通常由main函数,初始化和测试使用,并作为所有context的根节点。func Background() Context {   return background}// TODO返回一个非空的Context。TODO通常用在并不知道传递什么context的情形,代码应使用context.TODO。// 在很多的函数调用的过程中都会传递但是通常又不会使用,比如既不会监听退出,也不会从其中获取数据。// TODO跟Background一样,也是返回一个全局变量。func TODO() Context {   return todo}

Background

cancelCtx

cancelCtx结构体内嵌了一个Context对象,即其parent context,这样,它就可以被看成一个Context。

同时内部还通过children来保存所有可以被取消的context的接口,后续如果当前context被取消的时候,只需要调用所有canceler接口的context就可以实现当前调用链的取消。

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}

如果当前的goroutine持有的Context实例是可被Cancel的,那么它的所有child goroutine也应当是可被Cancel的,这也是cancelCtx类中存在Context字段和children字段的原因。

cancelCtx结构体字段解释:Context为父context;done字段是Done()方法的返回值;children中的key记录着所有的孩子,value是没有意义的;err字段是Err()方法的返回值。

Done方法的实现

func (c *cancelCtx) Done() struct{} {   c.mu.Lock()   // 内部变量 done “懒加载”,只有调用了 Done() 方法的时候才会被创建。   if c.done == nil {      c.done = make(chan struct{})   }   d := c.done   c.mu.Unlock()   return d}

Done操作返回当前的一个chan,用于通知goroutine退出。

cancel() 方法的实现

// 参数removeFromParent:是否需要把它从父节点的孩子中除名。// 将参数err赋值给字段err, 在方法Context.Err中返回。func (c *cancelCtx) cancel(removeFromParent bool, err error) {   if err == nil {      panic("context: internal error: missing cancel error")   }   // context一旦被某个操作操作触发取消后,就不会再进行任何状态的修改。   c.mu.Lock()   if c.err != nil {      c.mu.Unlock()      return // already canceled   }   c.err = err   // 关闭channel,通知其他协程。   if c.done == nil {      c.done = closedchan   } else {      close(c.done)   }   // 递归调用所有children取消。   for child := range c.children {       // 当c调用返回的cancelFunc时,执行cancel方法,会将c从它的父节点里移除(见[2])。       // 而c所有的children都会因为c.children = nil移除(见[1]),无需一个个处理。      child.cancel(false, err)   }   // [1]   c.children = nil   c.mu.Unlock()      // 如果为true,从父节点中移除自己[2]。   if removeFromParent {      removeChild(c.Context, c)   }}// 全局复用的, 被关闭的channel。var closedchan = make(chan struct{})func init() {   close(closedchan)}// parentCancelCtx判断Context实例是否是一个可被Cancel的类型。// parentCancelCtx只识别context标准包内的三种类型。// 如果用户自己的类实现了context.Context接口,或者把ctx包在了自己的类型内,或者是emptyCtx,将执行default。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      }   }}// removeChild removes a context from its parent.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关闭c.done,取消c的每个children,如果removeFromParent为true,则从其父级的子级中删除c。

WithCancel方法

它可以创建一个可取消的context。

// 多个goroutine可以同时调用CancelFunc,在第一个调用之后,随后对CancelFunc的调用什么都不做。// 调用该函数意味着将关闭Context, 结束相关的work。type CancelFunc func()// 从parent上派生出一个新的Context,并返回一个CancelFunc类型的函数。// 当调用对应的cancel函数时,该Context对应的Done()返回的只读channel也会被关闭。// 取消Context将释放与其关联的资源,因此在此context中某项任务的操作完成后,代码应立即调用返回的cancel函数。func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {   c := newCancelCtx(parent)   propagateCancel(parent, &c)   return &c, func() { c.cancel(true, Canceled) }} // 下面是WithCancel两个内部方法的实现。func newCancelCtx(parent Context) cancelCtx {   return cancelCtx{Context: parent}}func propagateCancel(parent Context, child canceler) {   // 说明父节点是emptyCtx,或者用户自己实现的context.Context。   if parent.Done() == nil {      return // parent is never canceled   }   // 如果父Context本身是可Cancel的。   if p, ok := parentCancelCtx(parent); ok {      // 说明父Context是以下三种之一:      // 1. 是一个cancelCtx, 本身就可被Cancel;      // 2. 是一个timerCtx, timerCtx是canctx的一个子类, 也可被Cancel;      // 3. 是一个valueCtx, valueCtx继承体系上的某个爹, 是以上两者之一。      p.mu.Lock()      if p.err != nil {         // parent has already been canceled         // 父节点已经被取消了,本节点(子节点)也要取消。         child.cancel(false, p.err)      } else {         // 若父节点未取消, 就把子节点添加到它的children列表中去。         if p.children == nil {            p.children = make(map[canceler]struct{})         }         p.children[child] = struct{}{}      }      p.mu.Unlock()   } else {      // 说明父Context虽然可被Cancel,但并不是标准库中预设的cancelCtx或timerCtx两种可被Cancel的类型。      // 这意味着这个特殊的父Context, 内部并不能保证记录了所有儿子的列表。      // 新开一个goroutine, 时刻监视着父Context的生存状态。      go func() {         select {         // 当父Context取消, 就立即调用child.cancel处理子节点。         case Done():            child.cancel(false, parent.Err())         // 如果子节点自己取消了,那就退出这个select,父节点的取消信号就不用管了。         // 如果去掉这个case,那么很可能父节点一直不取消,这个goroutine就泄漏了。         // 如果父节点取消了,子节点就会重复取消。         case Done():         }      }()   }}

调用cancel方法的时候,第一个参数是true,在取消的时候需要将自己从父节点里删除。第二个参数Canceled是一个固定的取消错误类型。

// Canceled is the error returned by Context.Err when the context is canceled.var Canceled = errors.New("context canceled")

timerCtx

带有超时的contex。

timerCtx继承了cancelCtx接口,同时还包含一个timer.Timer定时器(由标准库的time.Timer实现)和一个deadline终止实现。Timer会在deadline到来时,自动取消context。

type timerCtx struct {   cancelCtx   timer *time.Timer // Under cancelCtx.mu.   deadline time.Time}

Deadline()方法

它只是简单的字段deadline的getter。

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

cancel()取消方法

它重写了canceler.cancel方法。

cancel()方法首先进行cancelCtx的取消流程,然后进行自身的定时器的Stop操作。

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()}

WithDeadline()方法

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {   if cur, ok := parent.Deadline(); ok && cur.Before(d) {      // 如果父节点也是一个有dealine的Context,而且dealine更靠前,以父节点的为准。      // 一旦父节点超时,子节点也会随之取消。      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(false, Canceled) }   }   c.mu.Lock()   defer c.mu.Unlock()   if c.err == nil {      // d时间后,timer会自动调用cancel函数,当前的goroutine不会被阻塞。      c.timer = time.AfterFunc(dur, func() {         c.cancel(true, DeadlineExceeded)      })   }   return c, func() { c.cancel(true, Canceled) }}

可以看到,timerCtx只是对cancelCtx在功能上的追加WithDeadline也只是简单的追加了一个定时器。

如果要创建的这个子节点的deadline比父节点要晚,子节点在deadline 到来之前就已经被父节点取消了。

timer调用cancel函数传入的参数如下。

var DeadlineExceeded error = deadlineExceededError{}type deadlineExceededError struct{}func (deadlineExceededError) Error() string   { return "context deadline exceeded" }

另外,标准包还提供了一个WithTimeout函数,其实与WithDeadline是等价的。

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {   return WithDeadline(parent, time.Now().Add(timeout))}

WithTimeout函数调用了WithDeadline,传入的deadline是当前时间加上timeout的时间,需要用的是绝对时间。

valueCtx

带数据共享的非公开类valueCtx。其内部通过一个key/value进行值的存储,并且只能存储一个key(可比较的key)。

如果当前context不包含值则进行层层向上递归(父节点没法获取子节点存储的值,子节点却可以获取父节点的值)。

type valueCtx struct {   Context   key, val interface{}}func WithValue(parent Context, key, val interface{}) Context {   if key == nil {      panic("nil key")   }   if !reflectlite.TypeOf(key).Comparable() {      panic("key is not comparable")   }   return &valueCtx{parent, key, val}}func (c *valueCtx) String() string {   return contextName(c.Context) + ".WithValue(type " +      reflectlite.TypeOf(c.key).String() +      ", val " + stringify(c.val) + ")"}func (c *valueCtx) Value(key interface{}) interface{} {   if c.key == key {      return c.val   }   return c.Context.Value(key)}

参考资料

[1]  深度解密Go语言之context:

https://qcrao.com/2019/06/12/dive-into-go-context

[2]  Go context:

https://github.com/cch123/golang-notes/blob/master/context.md

[3]  图解Go语言的context了解编程语言核心实现源码:

https://www.cnblogs.com/buyicoding/p/12155169.html

[4]  golang中的context包:

https://www.cnblogs.com/neooelric/p/10668820.html

知秋君
上一篇 2024-07-29 22:36
下一篇 2024-07-29 22:02

相关推荐