博客
关于我
Golang Context源码解析
阅读量:798 次
发布时间:2023-04-16

本文共 6460 字,大约阅读时间需要 21 分钟。

自从接触Go语言以来,Context就一直在用,不过一直弄不清楚这东西究竟有啥用,平常都是CV代码,最多就是稀里糊涂用个Background()。不过最近在开发Golang反向代理的时候遇到一个传值问题,因为调用的是底层的库不支持传递变量,所以甚是困惑,后来在stackoverflow搜索了一番,发现已有类似问题,给出的答案很简单,就是通过Context的WithValue传值,而且底层库自身的参数也包含Context,所以困惑的问题一下就解决了。回过头来想一下,如此简单明显的问题都不会,说明对Golang了解的过于片面,这可不是一个优秀工程师应有的表现。然后就看了下Context包的源码。

Context包其实写的挺简单的,代码量也不多,很适合阅读。看完之后给我感觉就是Context就像一个共享内存,在不同的goroutine之间共享一些信息,比如值、或者信号等等,设计比较简洁,代码也比较有参考价值。

下面直接看代码:

package contextimport (	"errors"	"internal/reflectlite"	"sync"	"sync/atomic"	"time")type Context interface {	Deadline() (deadline time.Time, ok bool)	Done() <-chan struct{}	Err() error	Value(key interface{}) interface{}}var Canceled = errors.New("context canceled")var DeadlineExceeded errortype emptyCtx struct{}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"	default:		return "unknown empty Context"	}}var (	background = new(emptyCtx)	todo       = new(emptyCtx))func Background() Context {	return background}func TODO() Context {	return todo}type CancelFunc func()func WithCancel(parent Context) (Context, CancelFunc) {	if parent == nil {		panic("cannot create context from nil parent")	}	c := newCancelCtx(parent)	propagateCancel(parent, c)	return c, func() {		c.cancel(true, Canceled)	}}type cancelCtx struct {	Context    *emptyCtx	mu       sync.Mutex	done     chan struct{}	err      error	children map[canceler]struct{}}func newCancelCtx(parent Context) *cancelCtx {	return &cancelCtx{		Context:    parent,		mu:       sync.Mutex{},		done:     nil,		err:      nil,		children: make(map[canceler]struct{}),	}}func propagateCancel(parent Context, child *cancelCtx) {	done := parent.Done()	if done == nil {		return	}	select {	case <-done:		child.cancel(false, parent.Err())		return	default:	}	p, ok := parentCancelCtx(parent)	if !ok {		atomic.AddInt32(&goroutines, +1)		go func() {			select {			case <-parent.Done():				child.cancel(false, parent.Err())			case <-child.Done():			}		}()		return	}	p.mu.Lock()	if p.err != nil {		child.cancel(false, p.err)	} else {		if p.children == nil {			p.children = make(map[canceler]struct{})		}		p.children[child] = struct{}{}	}	p.mu.Unlock()}func parentCancelCtx(parent Context) (*cancelCtx, bool) {	done := parent.Done()	if done == closedchan || done == nil {		return nil, false	}	p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)	if !ok {		return nil, false	}	p.mu.Lock()	ok = p.done == done	p.mu.Unlock()	if !ok {		return nil, false	}	return p, true}func removeChild(parent Context, child canceler) {	p, ok := parentCancelCtx(parent)	if !ok {		return	}	p.mu.Lock()	if p.children != nil {		delete(p.children, child)	}	p.mu.Unlock()}type canceler interface {	cancel(removeFromParent bool, err error)	Done() <-chan struct{}}var closedchan = make(chan struct{})func init() {	close(closedchan)}func (c *cancelCtx) Value(key interface{}) interface{} {	if key == &cancelCtxKey {		return c	}	return c.Context.Value(key)}func (c *cancelCtx) Done() <-chan struct{} {	c.mu.Lock()	if c.done == nil {		c.done = closedchan	} else {		close(c.done)	}	c.mu.Unlock()	return c.done}func (c *cancelCtx) Err() error {	c.mu.Lock()	err := c.err	c.mu.Unlock()	return 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	}	c.err = err	if c.done == nil {		c.done = closedchan	} else {		close(c.done)	}	for child := range c.children {		child.cancel(false, err)	}	c.children = nil	c.mu.Unlock()	if removeFromParent {		removeChild(c.Context, c)	}}type timerCtx struct {	cancelCtx: *cancelCtx	deadline time.Time}func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {	return c.deadline, true}func (c *timerCtx) String() string {	return contextName(c.cancelCtx.Context) + ".WithDeadline(" + c.deadline.String() + " [" + time.Until(c.deadline).String() + "])"}func (c *timerCtx) cancel(removeFromParent bool, err error) {	c.cancelCtx.cancel(false, err)	if removeFromParent {		removeChild(c.cancelCtx.Context, c)	}	c.mu.Lock()	if c.timer != nil {		c.timer.Stop()		c.timer = nil	}	c.mu.Unlock()}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) {		return WithCancel(parent)	}	c := &timerCtx{		cancelCtx: newCancelCtx(parent),		deadline: d,	}	propagateCancel(parent, c)	if d <= time.Until(d) {		c.cancel(true, DeadlineExceeded)		return c, func() {			c.cancel(false, Canceled)		}	}	c.mu.Lock()	defer c.mu.Unlock()	if c.err == nil {		c.timer = time.AfterFunc(d, func() {			c.cancel(true, DeadlineExceeded)		})	}	return c, func() {		c.cancel(true, Canceled)	}}type stringer interface {	String() string}func contextName(c Context) string {	if s, ok := c.(stringer); ok {		return s.String()	}	return reflectlite.TypeOf(c).String()}func (c *cancelCtx) String() string {	return contextName(c.Context) + ".WithCancel"}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{		Context: parent,		key:   key,		val:   val,	}}func (c *valueCtx) String() string {	return contextName(c.Context) + ".WithValue(type " + reflectlite.TypeOf(c.key).String() + ", val " + stringify(c.val) + ")"}func stringify(v interface{}) string {	switch s := v.(type) {	case stringer:		return s.String()	case string:		return s	}	return "
\""}func (c *valueCtx) Value(key interface{}) interface{} { if c.key == key { return c.val } return c.Context.Value(key)}

Context包定义了两个私有指针变量background和todo,这两者都是emptyCtx的实例。Background()返回background,TODO()返回todo。自定义取消函数类型CancelFunc用于停止当前工作,且无需等待。WithCancel方法返回一个cancelCtx和一个取消函数,cancelCtx是Context的核心结构,用于管理子Context的生命周期。

通过阅读Context包的源码,我深刻理解了Context的设计理念。它不仅仅是一个传值机制,更是一个管理协程生命周期、传递信号和截止时间的强大工具。Context的设计简洁高效,适合复杂的并发编程环境。

了解了这些之后,我对Golang的Context功能有了更深入的认识。它在实际应用中能够有效地解决并发编程中的通信和资源管理问题,是一个非常实用的工具。通过实践和深入研究,我相信自己能够更好地利用Context来优化日常开发中的代码结构,提高程序的效率和可维护性。

转载地址:http://ybgfk.baihongyu.com/

你可能感兴趣的文章
MySQL5.1安装
查看>>
mysql5.5和5.6版本间的坑
查看>>
mysql5.5最简安装教程
查看>>
mysql5.6 TIME,DATETIME,TIMESTAMP
查看>>
mysql5.6.21重置数据库的root密码
查看>>
Mysql5.6主从复制-基于binlog
查看>>
MySQL5.6忘记root密码(win平台)
查看>>
MySQL5.6的Linux安装shell脚本之二进制安装(一)
查看>>
MySQL5.6的zip包安装教程
查看>>
mysql5.7 for windows_MySQL 5.7 for Windows 解压缩版配置安装
查看>>
Webpack 基本环境搭建
查看>>
mysql5.7 安装版 表不能输入汉字解决方案
查看>>
MySQL5.7.18主从复制搭建(一主一从)
查看>>
MySQL5.7.19-win64安装启动
查看>>
mysql5.7.19安装图解_mysql5.7.19 winx64解压缩版安装配置教程
查看>>
MySQL5.7.37windows解压版的安装使用
查看>>
mysql5.7免费下载地址
查看>>
mysql5.7命令总结
查看>>
mysql5.7安装
查看>>
mysql5.7性能调优my.ini
查看>>