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)
}
相关推荐
我不会起名字3221 小时前
一天一道算法题(29):单调栈
java·数据结构·python·算法·leetcode·golang·单调栈
FfHUCisI2 小时前
Golang 网络轮询器 netpoll:把阻塞 IO 变成事件通知
开发语言·网络·golang
李迟4 小时前
Golang实践录:工程框架实践:搭建工程模板
开发语言·后端·golang
FfHUCisI5 小时前
Golang 函数调用的瞬间:栈帧、拷贝栈与逃逸分析
开发语言·后端·golang
王的宝库5 小时前
Gin 框架实战
golang·gin
techdashen1 天前
Go Map 详解:键值对实际上是如何存储的
开发语言·后端·golang
ttwuai1 天前
Go 后台初始化 MySQL 脚本失败怎么办?先查 DDL 还是生成配置
开发语言·mysql·golang
名字还没想好☜1 天前
Go 的 TCP 粘包与拆包:用长度前缀协议 + bufio 正确读消息
后端·tcp/ip·golang·go·php
王的宝库2 天前
Go 项目结构:从单文件到标准工程布局
开发语言·后端·golang
花酒锄作田2 天前
Go - Gin中使用sessions
golang