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)的方式来控制协程》