golang基础(52.通过 context 包提供的函数实现多协程之间的协作)
除此之外,我们还可以通过另一种工具实现类似需求,这就是我们今天要介绍的 context 包,这个包为我们提供了以下方法和类型:
我们可以先通过 withXXX 方法返回一个从父 Context 拷贝的新的可撤销子 Context 对象和对应撤销函数 CancelFunc,CancelFunc 是一个函数类型,调用它时会撤销对应的子 Context 对象,当满足某种条件时,我们可以通过调用该函数结束所有子协程的运行,主协程在接收到信号后可以继续往后执行。
package main
import (
"context"
"fmt"
"time"
)
func go2(ctx context.Context) {
select {
case <-ctx.Done():
println("携程2已结束")
return
}
}
func go1(ctx context.Context) {
go go2(ctx)
select {
case <-ctx.Done():
println("携程1已结束")
return
}
}
func main() {
ctx, cancelFunc := context.WithCancel(context.Background())
go go1(ctx)
for i := 1; i < 100; i++ {
if i > 10 {
cancelFunc()
}
}
time.Sleep(1 * time.Second)
fmt.Println("主携程结束")
}
上述代码中,我们调用了`content.WithCancel()`,方法中返回了一个新的上下文以及一个可撤销函数`cancelFunc`。调用`cancelFunc`会在`ctx.Done()`管道中发送数据,其最终作用是发送一个信号。调用`cancelFunc`不代表子携程被中止。我们仍然需要结合`select`和`ctx.Done()`的方式来结束携程。<br />`context.WithTimeout`与`content.WithCancel()`的用处一致,但多封装了一层过期时间,传入时间后我们不需要手动执行`cancelFunc`,等到计时结束后会自动往`ctx.Done()`发送信号。
package main
import (
"context"
"fmt"
"time"
)
func go1(ctx context.Context) {
ch := make(chan bool)
go func() {
time.Sleep(20 * time.Microsecond)
ch <- true
}()
select {
case <-ch:
fmt.Println("正常结束")
return
case <-ctx.Done():
fmt.Println("go1 手动结束或超时")
return
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Microsecond)
go go1(ctx)
i := 0
for {
i++
time.Sleep(10 * time.Microsecond)
if i > 10000 {
fmt.Println("手动结束")
cancel()
break
}
}
time.Sleep(1 * time.Second)
}