介绍
Context 标准库是 go1.7 版本之后引入的,中文叫 goroutine 的上下文。包含 goroutine 的运行状态、环境等信息。
主要作用是:goroutine 之间传递上下文信息,如
- 取消信号
- 超时信号
- 截止时间信
- Key-value 值。
作用
传递 key-value 值
主要时候通过 context 包的 withvalue 方法传入 parent 上下文对象和 key-value,然后通过 value 方法获取到 key 的值。
如果是根的上下文,它的 parent 上下文为空,也就是传入 context.Background()
package main
import (
"context"
"fmt"
"time"
)
func g(ctx context.Context) {
// 传进来的ctx不可变
fmt.Println(ctx.Value("begin"))
fmt.Println("你是猪")
// ctx 作为g2协成的parent
go g2(context.WithValue(ctx, "movie", "正义联盟"))
}
func g2(ctx context.Context) {
fmt.Println(ctx.Value("movie"))
fmt.Println("电影很好看")
}
func main() {
// 根的上下文,所有父的上下文是空的,也就是context.Background()
ctx := context.WithValue(context.Background(), "begin", "从台词里看到一句话:")
go g(ctx)
time.Sleep(time.Second)
}
context 无限嵌套,可以获取父上下文的值吗?
是可以的,如下所示
ctx := context.Background()
ctx = context.WithValue(ctx, "one", 1)
ctx = context.WithValue(ctx, "two", 2)
// 会递归获取到父上下文的值的
fmt.Println(ctx.Value("one")) // 输出1
取消信号
主要是通过 WithCancel 方法来实现,该方法会返回两个值,一个是上下文对象,另外一个是函数,可以调用传递取消信号给各个协程,每个协程里可以通过 select 来监听信号,从而结束协程
package main
import (
"context"
"log"
"time"
)
func g1(ctx context.Context) {
go g2(ctx)
select {
// 只要能够从channel里取值出来,就代表外部传递了cancel信号了,具体是什么值不重要
case <-ctx.Done():
log.Println("g1 被取消了")
// cancel() 不需要cancle
return
}
}
func g2(ctx context.Context) {
ctx2, cancel := context.WithCancel(ctx)
go g3(ctx2)
select {
// 只要能够从channel里取值出来,就代表外部传递了cancel信号了,具体是什么值不重要
case <-ctx.Done():
cancel() // ctx2传递cancel信号
log.Println("g2 被取消了")
return
}
}
func g3(ctx context.Context) {
ctx3, _ := context.WithCancel(ctx)
go g4(ctx3) // 不传递cancel信号,g4也会被取消,不知为啥
select {
// 只要能够从channel里取值出来,就代表外部传递了cancel信号了,具体是什么值不重要
case <-ctx.Done():
log.Println("g3 被取消了")
return
}
}
func g4(ctx context.Context) {
select {
// 只要能够从channel里取值出来,就代表外部传递了cancel信号了,具体是什么值不重要
case <-ctx.Done():
log.Println("g4 被取消了")
return
}
}
func main() {
// 返回两个值,cancel是个函数,用于传递信号
ctx, cancel := context.WithCancel(context.Background())
go g1(ctx)
count := 1
for {
if count >= 3 {
// 外部手动执行cancel,把信号通过channel的形式传递给协程g
// g1、g2、g3、g4 都会被取消
cancel()
}
time.Sleep(time.Second)
count++
}
}
超时取消
通过 WithTimeout 方法来设置超时时间,如果协程执行的时间超时了,会自动传递 cancel 信号给协成,不需要自己手动调用 cancel 方法。
下例是手动取消和超时取消混合使用:
package main
import (
"context"
"log"
"time"
)
func g1(ctx context.Context) {
done := make(chan struct{}, 1)
go func() {
// 这边延迟了1s,所以会超时取消
time.Sleep(time.Second * 1)
done <- struct{}{}
}()
select {
case <-ctx.Done():
log.Println("g1 被取消或超时了")
// cancel() 不需要cancle
return
case <-done:
log.Println("g1 正常完成")
return
}
}
func main() {
// 500ms超时
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
go g1(ctx)
count := 1
for {
if count >= 3 {
log.Println("手动取消")
cancel()
count = 0
}
time.Sleep(time.Second)
if count > 0 {
count++
}
}
}