Go的context的问题
Go的context的问题
2017-05-29
最近被由context引发的一个bug坑得不轻,所以反思一下Go的context的问题。
context是隐式的约束,没有检测
如果我们写一个函数,比如:
func f(a int, b []byte) {
}
我们知道它需要哪些参数,编译器是会帮我做检查的,当我调用
f(3, "sdfsdf")
它就会报错。
可是如果是context,就变成了一种隐式的约束,编译器不会帮我们做检查,比如:
func f(ctx context.Context) {
a := ctx.Value("a").(int)
b := ctx.Value("b").([]byte)
}
Value函数并没有任何保证,编译器不会检查传进来的参数是否是合理。然而f在什么样的上下文里面被调用是不确定的,因此检测被移到了运行时来做。
现在的函数f有一个隐式的约束,它需要从context里面传a和b两个参数,这些信息,在函数f的签名里面都没法体现。 如果我看一个函数,看它的签名没用,还得去读它的实现,这不是扯淡么!
context的锁争用
context是一层一层往下传的,如果全局都是使用同一个传递下来的context,会出现一个问题:锁争用。
select {
case <-context.Done():
}
大家都在同一个对象上面调用的Done函数,channel操作最终会加锁。这个是在etcd项目里面发现的一个问题,他们改了我们也跟着改了。在起goroutine的时候,一般不要用原来的context了,而是新建一个context,原始的context作为父context。这样不同goroutine就不会抢同一个锁。
一般是用的context.WitCancel()这个函数:
go func() {
ctx, cancel = context.WithCancel(ctx)
doSomething(ctx)
cancel()
}
调用WithCancel的时候,会得到一个新的子context,以及一个cancel函数。子ctx会在父context的Done函数收到信号,或者cancel被调用的情况下收到Done信号。
cancel是需要调用,它使得context释放相应的资源。开头提到的bug,就是这个地方被坑到了:这样写代码之后其实有一个假定的约束,即doSomething操作是一个同步的,当它返回以后,相应的context就已经结束了。
然后,我们的代码在doSomething里面函数调了很深之后(a调b,b调c,c调d),里面有一个开goroutine异步做的操作,于是就傻逼了。那个异步的操作还没完成,就被cancel掉了。
但是这个问题非常难查,为什么?因为单独看两个地方的代码片断,都没有看出任何问题。上面那段代码写的没问题呀,只要doSomething是一个同步操作就行。而看doSomething的逻辑也没问题,它调了其它函数,其它函数继续调更深的函数,只是到了那里,并没有任何关于禁止异步操作的约束说明。
不要将任何context保存为成员变量
context的标准用法就是每次都产生一个,然后一层一层往下传。注意,禁止将context捕获了存储下来。不要将任何context保存为成员变量,不要重用它们。
比如,我要做一个sender对象,它有一个Send方法。那么我不能在new的时候把ctx保存下来,在Send的时候使用:
func NewSender(ctx context.Context) *sender {
return &sender {
ctx: ctx,
}
}
func(s *sender) Send() {
grpc.XXX(s.ctx)
}
如果调用某个库它需要传一个context,你应该给它当时的上下文,如果没有,可以传context.Background(),但是不要像上面那样,创建对象的时候把context保存下来,到对象的方法调用的时候使用。
正确的使用姿势不应该看到context被保存到任何成员变量里面。
context的作为本质上是动态作用域
上面说到不要将context保存。让我们看一看问题的本质:
obj = new Object(ctx)
obj.method(ctx)
请问这是同一个上下文么? No! 一个时创建时的上下文,一个是运行时的上下文。其实正确来写,它们是这样子的:
obj = new Object(ctx1)
obj.method(ctx2)
那么把ctx1保存下来,给到ctx2用,当然不对。
被坑几次之后会觉得context很难用。我想了一下,其实这个问题跟动态作用域很类似。现代主流编程语言里面,没有任何一个采用动态作用域的,而人们大多习惯了词法作用域,所以思维上很难接受。
正好说一下动态作用域:
func f() {
a := 3
func g() int {
return a
}
}
采用词法作用域的语言,无论在哪里调用g(),返回的结果都是3。而采用动态作用域的语言,行为完全无法推断:
a := 7
g() // 这里返回的是7,a的值是看运行时绑定的,而不是声明时
a := 3
g() // 这里返回的是3
当你看到函数需要的参数是一个context,可以context是在每次运行时都不同了,仅仅看声明并没有什么信息,是不是很像动态作用域?
Go的context的问题的更多相关文章
- Javascript 的执行环境(execution context)和作用域(scope)及垃圾回收
执行环境有全局执行环境和函数执行环境之分,每次进入一个新执行环境,都会创建一个搜索变量和函数的作用域链.函数的局部环境不仅有权访问函数作用于中的变量,而且可以访问其外部环境,直到全局环境.全局执行环境 ...
- spring源码分析之<context:property-placeholder/>和<property-override/>
在一个spring xml配置文件中,NamespaceHandler是DefaultBeanDefinitionDocumentReader用来处理自定义命名空间的基础接口.其层次结构如下: < ...
- spring源码分析之context
重点类: 1.ApplicationContext是核心接口,它为一个应用提供了环境配置.当应用在运行时ApplicationContext是只读的,但你可以在该接口的实现中来支持reload功能. ...
- CSS——关于z-index及层叠上下文(stacking context)
以下内容根据CSS规范翻译. z-index 'z-index'Value: auto | <integer> | inheritInitial: autoApplies to: posi ...
- Tomcat启动报错org.springframework.web.context.ContextLoaderListener类配置错误——SHH框架
SHH框架工程,Tomcat启动报错org.springframework.web.context.ContextLoaderListener类配置错误 1.查看配置文件web.xml中是否配置.or ...
- mono for android Listview 里面按钮 view Button click 注册方法 并且传值给其他Activity 主要是context
需求:为Listview的Item里面的按钮Button添加一个事件,单击按钮时通过事件传值并跳转到新的页面. 环境:mono 效果: 布局代码 主布局 <?xml version=" ...
- Javascript的“上下文”(context)
一:JavaScript中的“上下文“指的是什么 百科中这样定义: 上下文是从英文context翻译过来,指的是一种环境. 在软件工程中,上下文是一种属性的有序序列,它们为驻留在环境内的对象定义环境. ...
- spring源码分析之<context:component-scan/>vs<annotation-config/>
1.<context:annotation-config/> xsd中说明: <xsd:element name="annotation-config"> ...
- 【Android】 context.getSystemService()浅析
同事在进行code review的时候问到我context中的getSystemService方法在哪实现的,他看到了一个ClipBoardManager来进行剪切板存储数据的工具方法中用到了cont ...
- context:component-scan" 的前缀 "context" 未绑定。
SpElUtilTest.testSpELLiteralExpressiontestSpELLiteralExpression(cn.zr.spring.spel.SpElUtilTest)org.s ...
随机推荐
- Mac OS 的属性列表文件plist装换
Mac OS系统自身包含有转换plist的工具:plutil.其中-p是以human可读方式显示plist文件,而convert就是转换参数,其中支持的格式有:xml,二进制和json.下面拿一个实际 ...
- ASP.NET Core 2.0 使用NLog实现日志记录
1.安装NuGet包 1.Install-Package NLog.Web.AspNetCore 2.Install-Package NLog 在csproj中编辑: <PackageRefer ...
- 如何卸载Centos自带jdk
1.搜索安装的jdk: rpm -qa|grep jdk 结果如下: java-1.7.0-openjdk-1.7.0.45-2.4.3.3.el6.x86_64 java-1.6.0-openjdk ...
- spring 配置多数据源(mysql读写分离)
前段时间刚换了家新公司,然后看项目代码里用了数据库读写分离的架构,然后好奇扒了代码简单看了下,总体来说就是运用spring aop切面方式来实现的.看明白后就在自己的个人小项目里运用了下,测试OK,所 ...
- STL读书笔记
vector - 会自动增长的数组 vector又称为向量数组,他是为了解决程序中定义的数组是不能动态改变大小这个缺点而出现的.一般程序实现是在类创建的时候同时创建一个定长数组,随着数据不断被写入,一 ...
- C#WebService 出现No 'Access-Control-Allow-Origin' header is present on the requested resource
C#WebService 出现No 'Access-Control-Allow-Origin' header is present on the requested resource 解决办法: 在c ...
- Java虚拟机-类加载
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行检验.转换解析和初始化,最终形成了可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制.在Java语言里,类型的加载.连接和初始化过 ...
- SSRS 数据源访问Cube 无法创建订阅的解决方法
SSRS Report 的数据源可以直接放问SSAS 的Cube. 当报表的数据源设置成下图: 这样设置后,report 能够正常访问 Cube 并打开Report. 但是,如果我们需要添加数据驱动的 ...
- 【读英文文档】Whetting Your Appetite(刺激你的食欲)
如果你有很多工作是通过计算机来完成的,那么你一定希望其中的很多事情能够自动地实现.比方说,你希望在文本文件中实现查找和替换的功能,以某一种机制实现照片的重命名以及重新排序的功能,一个小型的数据库甚至是 ...
- Java多线程问题
一. Java多线程: Java给多线程编程提供了内置的支持.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务. 多线程是多任务的一种特别的形式,但多线 ...