golang context

作用:用于在go协程中 传递上下文、超时、取消、传值

底层实现:是由互斥锁、channel、map来实现的

互斥锁:保护临界资源

channel: 用于信号通知,比如ctx.Done()

map: 保存父ctx下派生的所有子ctx, 父ctx关闭,子ctx都关闭

实现的接口

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

空ctx

go 复制代码
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
}

cancel ctx

使用map保存所有子ctx,确保父ctx cancel后,子ctx也cancel

go 复制代码
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 WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	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}
}

func propagateCancel(parent Context, child canceler) {
	fmt.Println("propagateCancel")
	done := parent.Done()
	if done == nil {
		return // parent is never canceled
	}

	select {
	case <-done:
		// parent is already canceled
		child.cancel(false, parent.Err())
		return
	default:
	}

	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{})
			}
			// 保存子ctx
			p.children[child] = struct{}{}
		}
		p.mu.Unlock()
	} else {
		atomic.AddInt32(&goroutines, +1)
		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
	if c.done == nil {
		c.done = closedchan
	} else {
		close(c.done) // 关闭channel, 用于通知ctx.Done()
	}

	// 关闭所有子ctx
	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)
	}
}

timeout ctx

在cancelctx的基础上实现,只是多了个定时器自动调用cancel

go 复制代码
type timerCtx struct {
	cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
		// The current deadline is already sooner than the new one.
		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 {
		c.timer = time.AfterFunc(dur, func() {
			c.cancel(true, DeadlineExceeded)
		})
	}
	return c, func() { c.cancel(true, Canceled) }
}

value ctx

go 复制代码
type valueCtx struct {
	Context
	key, val interface{}
}
func WithValue(parent Context, key, val interface{}) Context {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	if key == nil {
		panic("nil key")
	}
	//	if !reflectlite.TypeOf(key).Comparable() {
	//	panic("key is not comparable")
	//}
	return &valueCtx{parent, key, val}
}
func (c *valueCtx) Value(key interface{}) interface{} {
	if c.key == key {
		return c.val
	}
	return c.Context.Value(key)
}
相关推荐
祁许10 小时前
【Golang】GORM - GEN工具 快速开始
开发语言·golang
showyoui10 小时前
深入Go语言之slice:不只是动态数组
后端·golang·slice·切片
余厌厌厌10 小时前
Go迭代器完全指南:从基础到实战
开发语言·golang
C墨羽14 小时前
使用Gin框架构建高并发教练预约微服务:架构设计与实战解析
微服务·架构·golang·gin
亚洲第一中锋_哈达迪2 天前
详解缓存淘汰策略:LFU
后端·缓存·golang
nextera-void2 天前
深入浅出 Golang:一次精神之旅
开发语言·golang·go
胡萝卜的兔2 天前
golang -gorm 增删改查操作,事务操作
开发语言·后端·golang
Go Dgg2 天前
【Go + Gin 实现「双 Token」管理员登录】
开发语言·golang·gin
nbsaas-boot2 天前
Go语言生态成熟度分析:为何Go还无法像Java那样实现注解式框架?
java·开发语言·golang
亚马逊云开发者2 天前
将 Go 应用从 x86 平台迁移至 Amazon Graviton:场景剖析与最佳实践
linux·数据库·golang