主动取消子线程组
真实的时间需要从 Playground 切换到 WebAssembly 下运行.
func worker(m context.Context, wg *sync.WaitGroup, name string) {
for {
select {
case <-m.Done():
fmt.Println(name + " done")
wg.Done()
return // exit 出函数而不是 break select
default:
fmt.Println(name + " alive")
time.Sleep(time.Second)
}
}
}
func main() {
m, cancel := context.WithCancel(context.Background())
wg := &sync.WaitGroup{}
wg.Add(2)
go worker(m, wg, "w1")
go worker(m, wg, "w2")
time.Sleep(3 * time.Second)
cancel() // 通知所有工人线程退出
wg.Wait()
}
程序定义了一个 worker 函数,该函数接收一个 context.Context 对象 m、一个 sync.WaitGroup 对象 wg 和一个字符串 name 作为参数。该函数会一直执行一个无限循环,这个循环中包含一个 select 语句。当 m.Done() 收到信号时,表示通知该函数退出循环,它会打印出当前工人名字的“done”消息,并使用 wg.Done() 函数告知 WaitGroup 等待的 goroutine 已完成,最后通过 return 语句来退出函数。如果没有收到任何信号,它会打印出当前工人名字的“alive”消息,并等待一秒钟。
在 main 函数中,它首先使用 context.Background() 函数创建了一个 context.Context 对象 m,然后使用 context.WithCancel() 函数创建了一个新的 Context 对象,用于在 cancel 调用后通知所有的 worker 函数退出循环。同时,它还创建了一个 sync.WaitGroup 对象 wg,并使用 wg.Add(2) 来告知 WaitGroup 需要等待两个 goroutine 执行完毕。接着,它启动两个 worker goroutine,分别传入 m、wg 和不同的名字作为参数。它会暂停三秒钟,然后调用 cancel() 函数来通知所有的 worker 函数退出循环。最后,它使用 wg.Wait() 函数等待所有 goroutine 执行完毕。
超时取消子线程组
真实的时间需要从 Playground 切换到 WebAssembly 下运行.
func worker(m context.Context, wg *sync.WaitGroup, name string) {
for {
select {
case <-m.Done():
fmt.Println(name + " done")
wg.Done()
return // exit 出函数而不是 break select
default:
fmt.Println(name + " alive")
time.Sleep(time.Second)
}
}
}
func main() {
m, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
wg := &sync.WaitGroup{}
wg.Add(2)
go worker(m, wg, "w1")
go worker(m, wg, "w2")
// 3秒后子线程组会退出
wg.Wait()
}
程序创建了两个工人(worker)线程,这些线程会不断地检查一个 context 对象,该对象表示一个取消信号。如果检测到该信号,工人线程将打印 "done" 并退出。否则,工人线程将打印 "alive" 并等待一秒钟。主线程等待3秒钟,然后通过调用context的cancel()函数通知所有工人线程退出。这个程序使用了Go语言的并发编程机制,并使用了 context 包来控制工人线程的退出。
cancel 函数是幂等的可多次调用
func main() {
m, cancel := context.WithCancel(context.Background())
go func() {
for {
select {
case <-m.Done():
fmt.Println("recevied...")
return
default:
fmt.Println("working...")
time.Sleep(500 * time.Millisecond)
}
}
}()
time.Sleep(time.Second)
cancel()
cancel()
cancel()
time.Sleep(time.Second)
}
程序创建了一个带有取消功能的上下文对象m和一个取消函数cancel。然后在一个匿名的goroutine里启动了一个无限循环,这个循环会在m对象的Done通道关闭时结束。在循环体内部,如果Done通道已经关闭,就打印received...并返回,否则就打印working...并休眠500毫秒。在main函数中,程序等待1秒钟然后调用了cancel函数三次,以确保调用多次cancel函数不会产生任何副作用。最后程序再等待1秒钟然后退出。
在线程组中传递值对象
// 为了 key 唯一性通常会定义一个 struct
type KeyStruct struct{}
// 为了在不同的 package 传递,key 通常会被导出
var Key = KeyStruct{}
func child1(ctx context.Context) {
ctx = context.WithValue(ctx, Key, "v1")
child2(ctx)
}
func child2(ctx context.Context) {
v := ctx.Value(Key)
ctx = context.WithValue(ctx, Key, v.(string)+"v2")
child3(ctx)
}
func child3(ctx context.Context) {
fmt.Println(ctx.Value(Key))
}
func main() {
ctx := context.Background()
child1(ctx)
}
程序演示了如何使用 context.Context 在函数间传递数据,同时保证在整个调用链路中都可以随时取消。
首先定义了一个用于标识 key 的结构体 KeyStruct,再定义一个导出的全局变量 Key 用于在不同的包之间传递数据。
接着定义了三个函数,child1、child2 和 child3。child1 函数通过调用 context.WithValue 创建了一个新的 context,并把 key 值设置为 "v1"。然后调用 child2 函数,并把这个新的 context 传递给它。
child2 函数首先调用 ctx.Value 获取 key 的值,然后再调用 context.WithValue 创建一个新的 context,并把 key 的值设置为之前的值加上 "v2"。最后调用 child3 函数并把这个新的 context 传递给它。
child3 函数通过调用 ctx.Value 获取 key 的值,并打印出来。
在 main 函数中,首先创建一个空的 context,并把它传递给 child1 函数,调用整个调用链路。最终 child3 函数输出了 "v1v2",这是由于在整个调用链路中,每个函数都通过 context.WithValue 修改了 key 的值。
设置 context 取消的原因
WithCancelCause 是 Go 1.20 中引入的一个新函数,用于在标准库的 context 包中写入和读取取消原因。这个函数允许在取消上下文时传递自定义错误类型,以便开发人员可以通过 errors.Is/As 函数来确定取消的原因,例如是否可重试等。
var ErrTemporarilyUnavailable = fmt.Errorf("service temporarily unavailable")
func main() {
ctx, cancel := context.WithCancelCause(context.Background())
// 操作失败,通过取消上下文通知调用者
cancel(ErrTemporarilyUnavailable)
switch ctx.Err() {
case context.Canceled:
fmt.Println("context cancelled by force")
}
// 获取取消的原因,这里是ErrTemporarilyUnavailable错误
err := context.Cause(ctx)
if errors.Is(err, ErrTemporarilyUnavailable) {
fmt.Printf("取消原因: %s", err)
}
// 输出: 取消原因: service temporarily unavailable
}