Go Context
Context通常被译作上下文,它是一个比较抽象的概念。在讨论链式调用技术时也经常会提到上下文。一般理解为程序单元的一个运行状态、现场、快照,而翻译中上下又很好地诠释了其本质,上下则是存在上下层的传递,上会把内容传递给下。在Go语言中,程序单元也就指的是Goroutine。
在Go语言中控制并发有两种经典的方式,一种是WaitGroup,另外一种就是Context。
Context业务场景
每个Goroutine在执行之前,都要先知道程序当前的执行状态,通常将这些执行状态封装在一个Context变量中,传递给要执行的Goroutine中。上下文则几乎已经成为传递与请求同生存周期变量的标准方法。在网络编程下,当接收到一个网络请求Request,在处理这个Request的goroutine中,可能需要在当前gorutine继续开启多个新的Goroutine来获取数据与逻辑处理(例如访问数据库、RPC服务等),即一个请求Request,会需要多个Goroutine中处理。而这些Goroutine可能需要共享Request的一些信息;同时当Request被取消或者超时的时候,所有从这个Request创建的所有Goroutine也应该被结束。
初识Context
上面说的这种场景是存在的,比如一个网络请求Request,每个Request都需要开启一个goroutine做一些事情,这些goroutine又可能会开启其他的goroutine。所以我们需要一种可以跟踪goroutine的方案,才可以达到控制他们的目的,这就是Go语言为我们提供的Context,称之为上下文非常贴切,它就是goroutine的上下文。
func main() { ctx, cancel := context.WithCancel(context.Background()) go func(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("监控退出,停止了...") return default: fmt.Println("goroutine监控中...") time.Sleep(2 * time.Second) } } }(ctx) time.Sleep(10 * time.Second) fmt.Println("可以了,通知监控停止") cancel() //为了检测监控过是否停止,如果没有监控输出,就表示停止了 time.Sleep(5 * time.Second) }
Context with用法
context.WithValue() 设置值传递
context.WithCancel() 设置关闭Groutine
context.WithTimeout() 设置超时时间
context.WithDeadine() 设置绝对过期时间
context.TODO() 返回一个非nil的空Context,当不清楚使用哪种Context或尚不可用时,应该使用context.TODO
context.Backgroup() 返回一个非nil的空Context,也是一个根Context(top-level Context)
// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming
// requests.
func Background() Context {
return background
}
Context控制多个goroutine
使用Context控制一个goroutine的例子如上,非常简单,下面我们看看控制多个goroutine的例子,其实也比较简单。
func main() { ctx, cancel := context.WithCancel(context.Background()) go watch(ctx,"【监控1】") go watch(ctx,"【监控2】") go watch(ctx,"【监控3】") time.Sleep(10 * time.Second) fmt.Println("可以了,通知监控停止") cancel() //为了检测监控过是否停止,如果没有监控输出,就表示停止了 time.Sleep(5 * time.Second) } func watch(ctx context.Context, name string) { for { select { case <-ctx.Done(): fmt.Println(name,"监控退出,停止了...") return default: fmt.Println(name,"goroutine监控中...") time.Sleep(2 * time.Second) } } }
示例中启动了3个监控goroutine进行不断的监控,每一个都使用了Context进行跟踪,当我们使用cancel
函数通知取消时,这3个goroutine都会被结束。这就是Context的控制能力,它就像一个控制器一样,按下开关后,所有基于这个Context或者衍生的子Context都会收到通知,这时就可以进行清理操作了,最终释放goroutine,这就优雅的解决了goroutine启动后不可控的问题。
Context传递消息
package main import ( "context" "fmt" "time" "sync" ) func worker(ctx context.Context, wg sync.WaitGroup) { traceCode, ok := ctx.Value("TRACE_CODE").(string) if ok { fmt.Printf("trace_code:%s\n", traceCode) } for { fmt.Printf("worker, trace_code:%s\n", traceCode) select { case <- ctx.Done(): return default: time.Sleep(time.Second) } } wg.Done() } func main() { ctx, cancel := context.WithCancel(context.Background()) ctx = context.WithValue(ctx, "TRACE_CODE", "12345678") var waitGroup sync.WaitGroup go worker(ctx, waitGroup) time.Sleep(3 * time.Second) cancel() waitGroup.Wait() }
转载至:
http://www.flysnow.org/2017/05/12/go-in-action-go-context.html
https://studygolang.com/articles/13343