解决的问题
在很多实际情况,比如处理网络请求时,我们需要启动多个goroutine来处理不同的逻辑,比如一个主要的goroutine用来响应请求,生成网页,同时它还启动一个子线程用来获取数据库信息,还有一个则写日志等等。正常情况都没有问题,但是一旦出现异常,如何优雅的退出这些子线程,同时释放掉可能占用的资源呢?
context
在golang中,人们发明了context接口处理这种情况。早在14年,这个库就出现了,并且提出了基于context的并发编程范式(英文好的同学可以直接撸这篇文章)。
今年8月go1.7发布后,它正式成为了标准库的一员。
如何使用
在golang的context库中,首先定义了context的接口,然后给出了context接口的4种实现:
WithCancel(parent Context) (Context, CancelFunc)
初始化一个可以被cancel的context,同时把新context对象作为child放入parent的children数组中。当parent终止时,child也会接受到信号。这个过程叫propagateCancel
WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
同样初始化一个context,除了实现跟WithCancel
同样的功能外,还增加了一个时间变量,一旦当前时间超过这个deadline,那么这个context以及它的所有子孙都被被cancel。WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
跟WithDeadline
类似,如果说WithDeadline
是一个绝对时间上的限制,那么WithTimeout
就是一个相对时间的限制WithValue(parent Context, key, val interface{}) Context
单纯给parent增加value,不需要propagateCancel
。value可以用来跨进程、跨api的传递数据,最好是和某个请求相关的参数,不要传递太多大量数据。
所以关键就在于propagateCancel
,实际工程中,所有context共同组成了一个依赖树,他们都继承自一个祖先。一旦parent被cancel,就会通过propagateCancel
递归的传播给下面的所有子孙。可以看出,context就好比信使,或者说通讯协议,通过遵循context接口构建的这个框架,能够保证子线程及时获得与他相关的父线程的状态,从而由子线程根据情况作出反应。至于怎么反应,就取决于各位码农的能力和搬砖当时的心情了。。。
另外,golang有一套静态分析工具可以分析context的传播过程,所以为了方便这个工具的使用,实际使用中有几个规定:
不要把context作为struct内部变量使用,而是把它和其他变量一块作为参数传入下一个函数。
context变量需要作为函数的第一个参数传入,命名一般为
ctx
具体例子
这个例子来源于基于context的并发编程范式,但是为了符合国情我做了些修改:
包括3部分:
server.go
主线程,会创建一个server服务器,可以通过localhost:9090/search
访问。接到请求后,它会创建父context,同时生成一个新goroutine,去fakesrv(本来应该去google上的)上请求数据。google.go
替换原来的google网址,改成由fakesrv提供的一个网址。主要就是演示一下context的运行过程,请求fakesrv的工作在一个新goroutine中进行,同时它还有一个访问数据库的操作。如果父context因为timeout超时了,那么对fakesrv和数据库的访问也会终止。在代码中,演示了如何监听context信息的过程。query.go
解析url中的query参数fakesrv.go
提供http://localhost:9000/context…供google.go访问。
mycontext/serve.go
|
|
mycontext/google/google.go
|
|
mycontext/query/query.go
|
|
mycontext/fakesrv/main.go
|
|
Makefile
|
|
测试
在命令行运行如下命令,即可看到具体结果
make run
make test