深入理解Go Context
在Go语言并发编程中,用一个goroutine来处理一个任务,而它又会创建多个goroutine来负责不同子任务的场景非常常见。如下图
这些场景中,往往会需要在API边界之间以及过程之间传递截止时间、取消信号或与其它请求相关的数据
谁是性能卡点呢?得通知它们任务取消了。
这时候就可以使用Context
了。context包在Go1.7的时候被加入到官方库中。
context包的内容可以概括为,一个接口,四个具体实现,还有六个函数。
Context接口提供了四个方法,下面是Context的接口
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
emptyCtx类型
emptyCtx本质上是一个整型, *emptyCtx对Context接口的实现,只是简单的返回nil,false,实际上什么也没做。如下代码所示:
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
}
Background和TODO这两个函数内部都会创建emptyCtx
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
func TODO() Context {
return todo
}
其中Background主要用于在初始化时获取一个Context(从代码中可知本质是一个*emptyCtx,而emptCtx本质上是一个Int),这就是Background()函数返回的变量结构。
而TODO()函数,官方文档建议在本来应该使用外层传递的ctx而外层却没有传递的地方使用,就像函数名称表达的含义一样,留下一个TODO。
cancelCtx类型
再来看cancelCtx类型,cancleCtx定义如下
// cancelCtx可以被取消。 取消后,它也会取消所有实现取消方法的子级。
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 (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 = make(chan struct{})
}
d := c.done
c.mu.Unlock()
return d
}
func (c *cancelCtx) Err() error {
c.mu.Lock()
err := c.err
c.mu.Unlock()
return err
}
这是一种可取消的Context,done用于获取该Context的取消通知,children用于存储以当前节点为根节点的所有可取消的Context,以便在根节点取消时,可以把它们一并取消,err用于存储取消时指定的错误信息,而这个mu就是用来保护这几个字段的锁,以保障cancelCtx是线程安全的。
而WithCancel函数,可以把一个Context包装为cancelCtx,并提供一个取消函数,调用它可以Cancel对应的Context
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) }
}
示例代码:
ctx := context.Background()
ctx1, cancel := context.WithCancel(ctx)
timerCtx类型
再来看timerCtx,timerCtx定义如下
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
它在cancelCtx的基础上,又封装了一个定时器和一个截止时间,这样既可以根据需要主动取消,也可以在到达deadline时,通过timer来触发取消动作。
要注意,这个timer也会由cancelCtx.mu来保护,确保取消操作也是线程安全的。
通过WithDeadline和WithTimeout函数,都可以创建timerCtx,区别是WithDeadline函数需要指定一个时间点,而WithTimeout函数接收一个时间段。
接下来,我们基于ctx1构造一个timerCtx
ctx := context.Background()
ctx1, cancel := context.WithCancel(ctx)
deadline := time.Now().Add(time.Second)
ctx2, cancel := context.WithDeadline(ctx1, deadline)
这个定时器会在deadline到达时,调用cancelCtx的取消函数,现在可以看到ctx2是基于ctx1创建的,而ctx1又是基于ctx创建的,基于每个Context可以创建多个Context,这样就形成了一个Context树,每个节点都可以有零个或多个子节点,可取消的Context都会被注册到离它最近的、可取消的祖先节点中。对ctx2来说离它最新的、可取消的祖先节点是ctx1
所以在ctx1这里的children map中,会增加ctx2这组键值对
如果ctx2先取消,就只会影响到以它为根节点的Context,而如果ctx1先取消,就可以根据children map中的记录,把ctx1子节点中所有可取消的Context全部Cancel掉。
最后来看valueCtx类型
valueCtx类型
首先来看valueCtx的定义
type valueCtx struct {
Context
key, val interface{}
}
它用来支持键值对打包,WithValue函数可以给Context附加一个键值对信息,这样就可以通过Context传递数据了
var keyA string = "keyA"
ctx := context.Background()
ctxA := context.WithValue(ctx, keyA, "valA")
现在我们给ctx附加一个键值对keyA=>valA,变量ctxA也是Context接口类型,动态类型为*valueCtx,data指向一个valueCtx结构体,第一个字段是它的父级Context,key和val字段都是空接口类型,keyA的动态类型为string,动态值是string类型的变量keyA,val的动态类型同样是string,动态值为valA,
下面我们再基于ctxA,附加一个key相等但val不相等的键值对keyA=>eggo,ctxC的动态值指向这样一个valueCtx,父级Context自然是ctxA,key与ctxA中的相同,但是val的值与ctxA中的不相等
通过ctxC获取kyA和keyC对应的值时会发现keyC覆盖了keyA对应的val,要找到原因,就要先看看Value方法是怎么工作的
func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key)
}
首先它会笔记当前Context中的key是否等于要查找的key,因为keyA等于keyC,所以对keyA的查找会直接锁定到ctxC这里的val,因而出现了子节点覆盖父节点数据的情况,为了规避这种情况,最好不要直接使用string、int这些基础类型作为Key,而是用自定义类型包装一下,就像下面这样,把keyA定义为keytypea类型,keyC定义为keytypec类型,这样再次通过ctxC获取keyA时,因为key的类型不相同,第一步key相等性比较不通过,就会委托父节点继续查找,进而找到正确的val
所以说valueCtx之间通过Context字段形成了一个链表结构,使用Context传递数据时还要注意,Context本身本着不可改变(immutable)的模式设计的,所以不要试图修改ctx里保存的值,在http、sql相关的库中,都提供了对Context的支持,方便我们在处理请求时,实现超时自动取消,或传递请求相关的控制数据等等
了解了context包中,一个接口,四种具体实现,以及六个函数的基本情况,有助于我们理解Context的工作原理
整理自:
深入理解Go Context的更多相关文章
- 理解Go Context机制
1 什么是Context 最近在公司分析gRPC源码,proto文件生成的代码,接口函数第一个参数统一是ctx context.Context接口,公司不少同事都不了解这样设计的出发点是什么,其实我也 ...
- 理解上下文Context
--摘自<Android进阶解密> 知识点: 1.Context的使用场景 1)使用Context调用方法,比如启动Activity.访问资源.调用系统级服务等 2)调用方法时传入Cont ...
- 全面理解Context
出处:http://blog.csdn.net/lmj623565791/article/details/40481055,本文出自:[张鸿洋的博客] 本文大多数内容翻译自:http://www.do ...
- 转:Android Context的理解
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40481055,本文出自:[张鸿洋的博客] 本文大多数内容翻译自:http://w ...
- 【get√】golang新手理解了一点点context
测试代码如下: package practice import ( "context" "log" //"fmt" "time&q ...
- Android Context上下文解析
1.Context概念 Context,相信不管是第一天开发Android,还是开发Android的各种老鸟,对于Context的使用一定不陌生~~你在加载资源.启动一个新的Activity.获取系统 ...
- Android Context 上下文 你必须知道的一切
本文转载于:http://blog.csdn.net/lmj623565791/article/details/40481055 转载请标明出处:http://blog.csdn.net/lmj623 ...
- Flask备注三(Context)
Flask备注三(Context) Flask支持不同的应用场景下,对应不同的local context(本地上下文环境),用来提供当前环境下的资源.lcoal context和全局变量以及局部变量最 ...
- 关于context你必须知道的一切
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40481055,本文出自:[张鸿洋的博客] 本文大多数内容翻译自:http://w ...
随机推荐
- element ui的el-radio踩坑
1.html 1 <div class="listPeopleDetail"> 2 <div class="item" v-for=" ...
- JVM命令手册
原文链接:https://blog.csdn.net/qq_41345773/article/details/93895532 aconst_null 将null对象引用压入栈iconst_m1 将i ...
- 【面试专栏】Java并发编程:volatile关键字
1. 内存模型 若一个变量在多线程环境下同时操作,则可能出现结果不一致的情况.这就是常说的缓存不一致性问题. 解决缓存不一致问题,通常有两个解决方案: 通过在总线加LOCK#锁的方式 因为CPU和其 ...
- JavaSE21-网络编程
1.网络编程入门 1.1 网络编程概述 计算机网络 是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共 ...
- [日常摸鱼]HDU1348Wall-凸包
我学习进度慢得连我自己都怕- 题意:大概给$n$个点搞出它的凸包,然后还要在凸包外弄一层厚为$l$的东西,求这个东西的周长 我个滞涨居然把pi开成了int-搞了一个晚上才看见 凸包直接求,因为是凸多边 ...
- Javascript之Firefox与IE
IE其实相对来讲并不是规范的遵循者,错怪firefox了. 2020注:IE看来要退出市场了,这些也逐渐成为历史了.:) 1firefox不支持iframe.document, 而IE支持,所以对fi ...
- 为什么 HashMap 的容量大小要设置为2的N次方?
原文链接:https://www.changxuan.top/?p=1208 前两天,我在一位同学提交中看到了下面这样的一行代码,让我很是惊讶. Map<String, String> t ...
- 设计模式——责任链(结合Tomcat中Filter机制)
设计模式:责任链模式 说责任链之前,先引入一个场景,假如规定学生请假小于或等于 2 天,班主任可以批准:小于或等于 7 天,系主任可以批准:小于或等于 10 天,院长可以批准:其他情况不予批准:以此为 ...
- 说说 Python 中的高阶函数
高阶函数(higher-order function)指的是:接受一个函数为参数,或者把函数作为结果值返回的函数. 1 sorted() 比较常见的高阶函数是 sorted(),其内部的关键字参数 k ...
- php学习之sqlite查询语句之多条件查询
一.PHP+Mysql多条件-多值查询示例代码: index.html代码:<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitio ...