context主要是用于多个协程之间的统一控制,主要包括统一取消和统一超时。下面是关于context对多个协程进行统一控制的示例:
假设有这样一个应用场景,一个公司(main)有一名经理(manager)和两名工人(worker),公司下班(main exit)有两种可能:一:工人(worker)的工作时间已经达到合同约定的最大时长;二:经理(manager)提前叫停收工。两种可能满足其中一个即可下班。
示例:
package main import ( "context" "fmt" "time" ) //worker工作的最大时长,超过这个时长worker自行收工无需等待manager叫停 const MAX_WORKING_DURATION = 5 * time.Second //达到实际工作时长后,manager可以提前叫停 const ACTUAL_WORKING_DURATION = 10 * time.Second func main() { ctxWithCancel, cancel := context.WithTimeout(context.Background(), MAX_WORKING_DURATION) go worker(ctxWithCancel, "[1]") go worker(ctxWithCancel, "[2]") go manager(cancel) <-ctxWithCancel.Done() //暂停1秒便于协程的打印输出 time.Sleep(1 * time.Second) fmt.Println("company closed") } func manager(cancel func()){ time.Sleep(ACTUAL_WORKING_DURATION) fmt.Println("manager called cancel()") cancel() } func worker(ctxWithCancel context.Context, name string) { for { select { case <-ctxWithCancel.Done(): fmt.Println(name, "return for ctxWithCancel.Done()") return default: fmt.Println(name, "working") } time.Sleep(1 * time.Second) } }输出:
[1] working [2] working [2] working [1] working [1] working [2] working [2] working [1] working [1] working [2] working [1] return for ctxWithCancel.Done() [2] return for ctxWithCancel.Done() company closed可见,这次下班是因为ctxWithCancel的计时器到点引起的。
把实际工作时长改成2秒,最大工作时长不变,再运行一次
//worker工作的最大时长,超过这个时长worker自行收工无需等待manager叫停 const MAX_WORKING_DURATION = 5 * time.Second //达到实际工作时长后,manager可以提前叫停 const ACTUAL_WORKING_DURATION = 2 * time.Second输出:
[1] working [2] working [2] working [1] working manager called cancel() [1] return for ctxWithCancel.Done() [2] return for ctxWithCancel.Done() company closed可见,worker只工作了2秒就被manager提前叫停了。
至于为什么要用context而不是用计时器加通道来实现,请见另一篇文章《Go语言:为什么要使用上下文(context)而不是计时器(timer)加通道(channel)的方式来控制协程》