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)
}
相关推荐
Wy_编程13 小时前
go中的协程Goroutine
开发语言·golang
会编程的土豆14 小时前
Go 语言中的 `new` 关键字(创建指针)
java·算法·golang
喵了几个咪15 小时前
Kratos 生态双定时器中间件:高精度 hptimer 与标准 cron 选型与实践
微服务·中间件·架构·golang·kratos
Achou.Wang17 小时前
Concurrency patterns - Go 并发模式
开发语言·后端·golang
存在morning17 小时前
【GO语言开发实践】三 GO 工程化快速上手
开发语言·后端·golang
Achou.Wang17 小时前
Go语言并发编程中的死锁防范与破解之道
服务器·开发语言·golang
子安柠17 小时前
深入理解 Go 反射:原理、实践与性能陷阱
开发语言·golang
福大大架构师每日一题17 小时前
ollama v0.24.0 更新:Codex App 正式接入、内置浏览器、评审模式与 MLX 采样器重构,带来哪些变化?
重构·golang
会编程的土豆18 小时前
Go ini 配置加载:`ini.MapTo` 详细解析
开发语言·数据库·golang
喵了几个咪19 小时前
Kratos KCP 传输中间件:游戏开发低延迟网络通信实战指南
微服务·中间件·golang·游戏开发·kratos