# Concurrency In Go
# Context
首先提出一个问题,"it is easy to go, but how to stop.",意思就是说,在golang使用go关键字并发非常简单,但是,我们如何去停止一个开启了的go routine。
我们带着这个问题来认识context,首先,我们要明确,正常停止一个go routine的方式只有一种,就是func return.当方法退出了自然go routine就停止了。OK,那我们先看下面代码:
## step.1
go func controlStop(){
//do something
//if done. call return
return
}()
那我们就把停止go rouine转换成为,如何控制func return的问题
## step.2
var stop =false
go func controlStop(stop *bool){
for{
if stop {
return
}else{
//do something
}
}
}(stop)
//another branch control
stop = true
我们可以通过上述的模式,即采用自旋加一个全局变量来控制是否返回。是否相比step.1好一些呢。
## step.3
var stop chan struct{}
go func controlStop(stop *chan struct{}){
for{
select {
case <-stop:
return
default://if cancel default,it will be blocked.
//do something
}
}
}(&stop)
我们通过一个chan实现了对go routine的控制
## step.4
如果能够把这种go routine之间的控制,通信封装起来,做为一个STL提供给开发者调用,那么是不是就比较方便,且有统一的接口。OK,context.Context核心概念就是这个,当然,它还有一些升级的机制,但是,本质上,都是在go concurrency中协助go routine的控制。
var log1 *log.Logger
func main() {
log1 = log.New(os.Stdout, "", log.Ltime)
controlStop()
}
func controlStop() {
log1.Println("start")
ctx, cancel := context.WithCancel(context.Background())
go doDB1Query(ctx)//启动一个go routine去查询DB1
doDB2Query()//查询DB2
cancel() //cancel db1
time.Sleep(time.Second)
}
//查询DB2
func doDB2Query() {
time.Sleep(time.Second * 3)
}
//查询DB1
func doDB1Query(ctx context.Context) {
log1.Println("do db querying.")
for {
select {
case <-ctx.Done():
log1.Println("ctx done.")
return
default:
time.Sleep(time.Second)
log1.Println("db1 waiting db2")
}
}
}
以上就是context的一个最简单的cancel模型。
当然,context还有以下API
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key interface{}, val interface{}) Context
如,deadline,就会自动超时done,不必等到主动cancel。
---
## usage
讲了context的作用以及怎么使用,再讲讲应用。
- 网络连接
//1、accept conn
//2、start go routine to do something.
//3、now conn is closed,can use context to stop all the go routine that associated with the conn.
意思就是:比如一个IOT网络连接,那么我们需要基于这个连接去做一系列的读写操作,其中某些操作还比较费时,那么,我们开启了go routine,有的负责读数据进行持久化,有的负责定时写数据回去(比较耗时),那么,当读的时候发现conn失效,就必须关闭conn,那么我们就可以基于context及时把写数据的go routine给结束掉,而不需要等到出现写错误才自动关闭。
- 请求
比如http请求,使用了context的超时机制,如果读取超时,那么就干嘛干嘛等等。
---
## impl
context的底层实际就是定时器加chan,通过封装这些东西,来实现context的一个控制。
---
## conclusion
go routine的调用是从上而下,是一种树形调用,意味着有root,还有无数的child,因此,context的设计上也符合树形,我们首先基于一个background context创建,而子routine再基于ctx创建更子的ctx,从而实现层次级别的应用上下文关系,这也是为何叫context。
context.WithDeadline(ctx, time.Now())
// Programs that use Contexts should follow these rules to keep interfaces
// consistent across packages and enable static analysis tools to check context
// propagation:
//
// Do not store Contexts inside a struct type; instead, pass a Context
// explicitly to each function that needs it. The Context should be the first
// parameter, typically named ctx:
// 不要把Context存在一个结构体当中,显式地传入函数。Context变量需要作为第一个参数使用,一般命名为ctx;
// func DoSomething(ctx context.Context, arg Arg) error {
// // ... use ctx ...
// }
//
// Do not pass a nil Context, even if a function permits it. Pass context.TODO
// if you are unsure about which Context to use.
//即使方法允许,也不要传入一个nil的Context,如果你不确定你要用什么Context的时候传一个context.TODO;
// Use context Values only for request-scoped data that transits processes and
// APIs, not for passing optional parameters to functions.
//使用context的Value相关方法只应该用于在程序和接口中传递的和请求相关的元数据,不要用它来传递一些可选的参数
// The same Context may be passed to functions running in different goroutines;
// Contexts are safe for simultaneous use by multiple goroutines.
//同样的Context可以用来传递到不同的goroutine中,Context在多个goroutine中是安全的
在子Context被传递到的goroutine中,应该对该子Context的Done信道(channel)进行监控,一旦该信道被关闭(即上层运行环境撤销了本goroutine的执行),应主动终止对当前请求信息的处理,释放资源并返回。
---
如果想更加深入了解,可参考官方的信息
https://blog.golang.org/pipelines
https://blog.golang.org/context
https://www.youtube.com/watch?v=QDDwwePbDtw
有疑问加站长微信联系(非本文作者))