前言
在 Go 1.7 版本之前,context 还是非编制的,它存在于 golang.org/x/net/context 包中。
Context
type Context interface {
Deadline() (deadline time.Time, ok bool) // 截止时间
Done() <-chan struct{} // cancel后返回,可读
Err() error // context被cancel的原因
Value(key interface{}) interface{} // 绑定到Context的值
}
当一个协程(goroutine)开启后,我们是无法强制关闭它的。常见的关闭协程的原因有如下几种:
- goroutine 自己跑完结束退出
- 主进程crash退出,goroutine 被迫退出
- 通过通道发送信号,引导协程的关闭。
第一种,属于正常关闭,不在今天讨论范围之内。
第二种,属于异常关闭,应当优化代码。
第三种,才是开发者可以手动控制协程的方法
func main() {
stop := make(chan bool)
go func() {
for {
select {
case <-stop:
fmt.Println("监控退出,停止了...")
return
default:
fmt.Println("goroutine监控中...")
time.Sleep(2 * time.Second)
}
}
}()
time.Sleep(10 * time.Second)
fmt.Println("可以了,通知监控停止")
stop<- true
//为了检测监控过是否停止,如果没有监控输出,就表示停止了
time.Sleep(5 * time.Second)
}
第三种可以使用,但是不够优雅,好用。在goroutine比较多情况下,难以维护。所以,出现了context:
package main
import (
"context"
"fmt"
"time"
)
func monitor(ctx context.Context, number int) {
for {
select {
// 其实可以写成 case <- ctx.Done()
// 这里仅是为了让你看到 Done 返回的内容
case v :=<- ctx.Done():
fmt.Printf("监控器%v,接收到通道值为:%v,监控结束。\n", number,v)
return
default:
fmt.Printf("监控器%v,正在监控中...\n", number)
time.Sleep(2 * time.Second)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
for i :=1 ; i <= 5; i++ {
go monitor(ctx, i)
}
time.Sleep( 1 * time.Second)
// 关闭所有 goroutine
cancel()
// 等待5s,若此时屏幕没有输出 <正在监控中> 就说明所有的goroutine都已经关闭
time.Sleep( 5 * time.Second)
fmt.Println("主程序退出!!")
}
同时,它还有几种常用继承衍生:
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, val interface{}) Context
Context 使用注意事项:
- 通常 Context 都是做为函数的第一个参数进行传递(规范性做法),并且变量名建议统一叫 ctx
- Context 是线程安全的,可以放心地在多个 goroutine 中使用。
- 当你把 Context 传递给多个 goroutine 使用时,只要执行一次 cancel 操作,所有的 goroutine 就可以收到 取消的信号
- 不要把原本可以由函数参数来传递的变量,交给 Context 的 Value 来传递。
- 当一个函数需要接收一个 Context 时,但是此时你还不知道要传递什么 Context 时,可以先用 context.TODO 来代替,而不要选择传递一个 nil。
- 当一个 Context 被 cancel 时,继承自该 Context 的所有 子 Context 都会被 cancel。