Context
前言
前面在“聊一聊http框架httprouter”的时候,有提到上下文的概念,上一个demo用来列举web框架中全局变量的传值和设置,还类比了Java Spring框架中的ApplicationContext
。
这一次我们就来聊一聊Go中的标准库的context,梳理上下文概念在go中的常用情景。
问题引入
在列举上下文的用法之前,我们来看一个简单的示例:
协程泄露
func main() {
//打印已有协程数量
fmt.Println("Start with goroutines num:", runtime.NumGoroutine())
//新起子协程
go Spawn()
time.Sleep(time.Second)
fmt.Println("Before finished, goroutines num:", runtime.NumGoroutine())
fmt.Println("Main routines exit!")
}
func Spawn() {
count := 1
for {
time.Sleep(100 * time.Millisecond)
count++
}
}
输出:
Start with goroutines num: 1
Before finished, goroutines num: 2
Main routines exit!
我们在主协程创建一个子协程,利用runtime.NumGoroutine()
打印当前协程数量,可以知道在main协程被唤醒之后退出前的那一刻,程序中仍然存在两个协程,可能我们已经习惯了这种现象,杀掉主协程的时候,退出会附带把子协程干掉,这种让子协程“自生自灭”的做法,其实是不太优雅的。
解决方式
管道通知退出
关于控制子协程的退出,可能有人会想到另一个做法,我们再来看另一个例子。
func main() {
defer fmt.Println("Main routines exit!")
ExitBySignal()
fmt.Println("Start with goroutines num:", runtime.NumGoroutine())
//主动通知协程退出
sig <- true
fmt.Println("Before finished, goroutines num:", runtime.NumGoroutine())
}
//利用管道通知协程退出
func ListenWithSignal() {
count := 1
for {
select {
//监听通知
case <-sig:
return
default:
//正常执行
time.Sleep(100 * time.Millisecond