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)
}
相关推荐
怪我冷i6 小时前
使用vscode调试wails项目(golang桌面GUI)
vscode·golang
小吴同学(wlx)10 小时前
Golang 进阶3—— 协程&管道
golang
技术卷10 小时前
GO网络编程(三):海量用户通信系统1:登录功能初步
golang·网络编程
虽千万人 吾往矣13 小时前
golang gorm
开发语言·数据库·后端·tcp/ip·golang
__AtYou__1 天前
Golang | Leetcode Golang题解之第448题找到所有数组中消失的数字
leetcode·golang·题解
千年死缓1 天前
go+redis基于tcp实现聊天室
redis·tcp/ip·golang
吃着火锅x唱着歌1 天前
Redis设计与实现 学习笔记 第五章 跳跃表
golang
技术卷2 天前
Redis数据库与GO完结篇:redis操作总结与GO使用redis
数据库·redis·golang
white.tie2 天前
vscode配置golang
ide·vscode·golang
陈序缘2 天前
Go语言实现长连接并发框架 - 任务管理器
linux·服务器·开发语言·后端·golang