我是靠谱客的博主 眯眯眼云朵,最近开发中收集的这篇文章主要介绍Golang context包源码分析,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

context源码分析

Golang JDK 1.10.3

 

Context介绍

      假如有多个goroutine来处理一个操作,这个操作有超时时间,当超时时间到了之后,如何通知这个操作的所有goroutine都退出。或者在操作的过程中有一个goroutine遇到异常需要退出,如何通知其他的goroutine也退出该操作? context就可以很好的解决以上问题。

     Go服务器的每个请求都有自己的goroutine,而有的请求为了提高性能,会经常启动额外的goroutine处理请求,当该请求被取消或超时,为了防止资源泄露,该请求上的所有goroutines应该退出。那么context来了,它对该请求上的所有goroutines进行约束,然后进行取消信号,超时等操作。

    context优点就是简洁的管理goroutines的生命周期。

 

Context源码分析

1. Context接口

type Context interface {
	Deadline() (deadline time.Time, ok bool)

	Done() <-chan struct{}

	Err() error

	Value(key interface{}) interface{}
}

      Dealine()方法 返回当前上下文的截至时间,即该上下文应该被取消的时间,如果没有设置超时时间,则返回的ok为false

 

      Done()方法 在Context被取消或超时时会close当前监听的channel,close的channel可以作为广播通知,告诉context相关的函数要停止当前工作然后退出。

      创建Context时会返回CancelFunc函数,调用CancelFunc函数以及到了超时时间,会关闭Done()方法监听的channel,所以Done()需要放到select中。

      父Context取消时,其子Context也会被取消。但是子Context取消时,其父Context不会被取消。

      Err()方法 如果Done未关闭,则Err返回nil,如果Done已关闭,则返回非空的error解释原因。

     Value方法通过key获取该key在上下文中关联的值,如果该key没有关联的值,则返回nil,多次调用同一个key,返回的结果是一样的。

 

2. emptyCtx

empty是空的Context,但是实现了Context的接口。emptyCtx没有超时时间,不能被取消,也不能存储任何额外信息,所以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"
}


func Background() Context {
	return background
}


func TODO() Context {
	return todo
}

由于emptyCtx是不能外部访问,所以我们只能用Background和TODO来使用。

Background经常会被使用,经常被当成根节点使用,而TODO在不确定使用什么Context时才会使用。

 

以下使用Background的示例,作为创建出来ctx的父节点

ctx, cancel := context.WithCancel(context.Background())

 

3.cancelCtx

       cancelCtx结构体实现了Context接口,同时也实现了canceler接口,cancelCtx是可以被取消的Context实现,也是context包的核心结构体。当cacel时,它会取消所有子节点中所有实现canceler接口的context。

type cancelCtx struct {
	Context      //指定父级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  
}

Context是指定父级Context

done 当调用取消函数或超时时,会通过close该channel,来通知上下文。

childer 记录所有实现canceler的子节点

err 第一次调用cancel后会设置一个非空的error,后面可以判断该值是否为空,来判断该上下文是否已经被取消

cancelCtx相关方法

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
}

Done()方法其实就是返回的该结构体中的done字段

 

func (c *cancelCtx) Err() error {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.err
}

返回错误信息

 

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
	if c.done == nil {  //如果c.done为nil,则初始化一个closed的chan
		c.done = closedchan
	} else { //不为空则关闭
		close(c.done)
	}
	for child := range c.children {  //cancel该context c的所有字节
		// NOTE: acquiring the child's lock while holding parent's lock.
		child.cancel(false, err)
	}
	c.children = nil  //将该context的children置为nil
	c.mu.Unlock()

	if removeFromParent { //是否删除其parent's context  的children中删除c
		removeChild(c.Context, c)
	}
}

该cancel方法是取消上下文的实现逻辑

(1). 取消当前节点的上下文

   如果当前节点的done为nil,则直接赋值一个关闭的channel。监听一个关闭的channel会直接返回。如果done不为nil,则close。

var closedchan = make(chan struct{})

func init() {
  close(closedchan)
}

closedchan初始化之后,就直接close。这样当监听该close 的channel时就会直接返回。

closedchan示例:

package main

import (
	"fmt"
)

func main() {
	ch := make(chan struct{})
	close(ch)

	select {
	case v, ok := <-ch:
		fmt.Println("ch")
		fmt.Println(v)
		fmt.Println(ok)
	default:
		fmt.Println("default")
	}
}

该示例会走 <-ch case

 

(2).  调用该节点的所有子节点的cancel来取消子节点,以及子节点的子节点.....

(3). removeFromParent为true,则会删除当前节点维护的子节点信息。

 

4.timerCtx结构体

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

	deadline time.Time   //超时时间点
}

从结构体中我们看到,timerCtxt继承了cancelCtx,是在cancelCtx结构体中提供了进一步的扩展。timerCtx可以调用取消函数进行取消,也可以设置超时,当超时时自动取消。

timer 是超时时执行的取消函数

deadline 记录超时的时间点。

 

func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {  //返回deadline,是否创建deadline
	return c.deadline, true
}

Deadline方法用于返回超时的时间点

 

func (c *timerCtx) cancel(removeFromParent bool, err error) {
	c.cancelCtx.cancel(false, err)  //取消该上下文
	if removeFromParent {    //删除该cancelCtx的parent的Context的children c
		// Remove this timerCtx from its parent cancelCtx's children.
		removeChild(c.cancelCtx.Context, c)
	}
	c.mu.Lock()
	if c.timer != nil {  //如果timer不为nil,则停止,避免重复执行cancel
		c.timer.Stop()
		c.timer = nil
	}
	c.mu.Unlock()
}

cancel方法主要调用cancelCtx的cancel进行取消context。

如果timer不为nil,则停止该定时任务,并置为nil。

 

5.初始化Context

上面我们提到Background()函数可以返回emptyCtx对象,常用来作为根节点。

其他对外提供的Context初始化方法为:WithCancel、WithDeadline和WithTimeout。

 (1). 通过WithCancel初始化Context

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	c := newCancelCtx(parent)   //初始化一个cancelCtx,并指定父级Context为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 {   //如果parent的Done()为nil,说明父级没有cancel,直接返回
		return // parent is never canceled
	}
	if p, ok := parentCancelCtx(parent); ok {  //parent的Contex实现类是cancelCtx
		p.mu.Lock()
		if p.err != nil {   //parent已经被取消
			// parent has already been canceled
			child.cancel(false, p.err)
		} else {
			if p.children == nil {  //如果p的children未初始化,则进行初始化
				p.children = make(map[canceler]struct{})
			}
			p.children[child] = struct{}{}   //指定改parent的children
		}
		p.mu.Unlock()
	} else {  //parent的Contex实现类不是是cancelCtx
		go func() {
			select {
			case <-parent.Done():  //当parent关闭时,关闭children
				child.cancel(false, parent.Err())
			case <-child.Done():
			}
		}()
	}
}

      WithCancel方法首先调用newCancelCtx初始化一个cancelCtx对象,并指定该对象的Context。

       propagateCancel方法的主要功能是如果指定的父节点是cancelCtx类型的,则将当前创建的节点维护到父节点中(通过父节点的children关联)。并且实现当父节点取消时,当前子节点也会被取消。

 

 (2). 通过WithDeadline初始化Context

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
	//ok:是否设置deadline,cur设置超时时间
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
		// The current deadline is already sooner than the new one.
		//设置超时时间 晚于 父级parent的超时时间,则调用WithCancel(parent)创建
		return WithCancel(parent)
	}
	c := &timerCtx{
		cancelCtx: newCancelCtx(parent),   //初始化cancelCtx
		deadline:  d,  //记录超时时间点
	}
	propagateCancel(parent, c)  //实现子级与父级关联,当父级cancel时,子级也cancel
	dur := time.Until(d)  //获取d到当前时间的duration
	if dur <= 0 {   //dur 小于 0,超时时间已过
		c.cancel(true, DeadlineExceeded) // deadline 已过,调用cancel函数取消
		return c, func() { c.cancel(true, Canceled) }
	}
	c.mu.Lock()
	defer c.mu.Unlock()
	if c.err == nil {  //到dur时间之后,调用c.cancel()函数,由系统新建goroutine执行
		c.timer = time.AfterFunc(dur, func() {
			c.cancel(true, DeadlineExceeded)
		})
	}
	return c, func() { c.cancel(true, Canceled) }
}

WithDeadline方法创建的Context,不仅可以通过手动调用取消函数取消,也可以指定超时的时间点,如果超时则会自动取消。主要通过timerCtx实现以上取消功能。

1. 如果创建的子节点的超时时间比父节点的超时时间还晚,则无需使用timerCtx创建,因为父节点取消时,该节点也会创建。所以直接调用WithCancel创建。

2. 如果晚于父节点,则会创建timerCtx对象,并调用propagateCancel实现与父节点的关联。

3. 如果超时时间已到,则直接调用取消函数。否则开启一个定时器到超时时间点触发取消函数。

 

 (3). 通过WithDeadline初始化Context

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

WithTimeout 等同于 WithDeadline(parent, time.Now().Add(timeout))

最后

以上就是眯眯眼云朵为你收集整理的Golang context包源码分析的全部内容,希望文章能够帮你解决Golang context包源码分析所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(35)

评论列表共有 0 条评论

立即
投稿
返回
顶部