除此之外,我们还可以通过另一种工具实现类似需求,这就是我们今天要介绍的 context 包,这个包为我们提供了以下方法和类型:
    52.通过 context 包提供的函数实现多协程之间的协作 - 图1
    我们可以先通过 withXXX 方法返回一个从父 Context 拷贝的新的可撤销子 Context 对象和对应撤销函数 CancelFunc,CancelFunc 是一个函数类型,调用它时会撤销对应的子 Context 对象,当满足某种条件时,我们可以通过调用该函数结束所有子协程的运行,主协程在接收到信号后可以继续往后执行。

    1. package main
    2. import (
    3. "context"
    4. "fmt"
    5. "time"
    6. )
    7. func go2(ctx context.Context) {
    8. select {
    9. case <-ctx.Done():
    10. println("携程2已结束")
    11. return
    12. }
    13. }
    14. func go1(ctx context.Context) {
    15. go go2(ctx)
    16. select {
    17. case <-ctx.Done():
    18. println("携程1已结束")
    19. return
    20. }
    21. }
    22. func main() {
    23. ctx, cancelFunc := context.WithCancel(context.Background())
    24. go go1(ctx)
    25. for i := 1; i < 100; i++ {
    26. if i > 10 {
    27. cancelFunc()
    28. }
    29. }
    30. time.Sleep(1 * time.Second)
    31. fmt.Println("主携程结束")
    32. }
    1. 上述代码中,我们调用了`content.WithCancel()`,方法中返回了一个新的上下文以及一个可撤销函数`cancelFunc`。调用`cancelFunc`会在`ctx.Done()`管道中发送数据,其最终作用是发送一个信号。调用`cancelFunc`不代表子携程被中止。我们仍然需要结合`select``ctx.Done()`的方式来结束携程。<br />`context.WithTimeout``content.WithCancel()`的用处一致,但多封装了一层过期时间,传入时间后我们不需要手动执行`cancelFunc`,等到计时结束后会自动往`ctx.Done()`发送信号。
    1. package main
    2. import (
    3. "context"
    4. "fmt"
    5. "time"
    6. )
    7. func go1(ctx context.Context) {
    8. ch := make(chan bool)
    9. go func() {
    10. time.Sleep(20 * time.Microsecond)
    11. ch <- true
    12. }()
    13. select {
    14. case <-ch:
    15. fmt.Println("正常结束")
    16. return
    17. case <-ctx.Done():
    18. fmt.Println("go1 手动结束或超时")
    19. return
    20. }
    21. }
    22. func main() {
    23. ctx, cancel := context.WithTimeout(context.Background(), 20*time.Microsecond)
    24. go go1(ctx)
    25. i := 0
    26. for {
    27. i++
    28. time.Sleep(10 * time.Microsecond)
    29. if i > 10000 {
    30. fmt.Println("手动结束")
    31. cancel()
    32. break
    33. }
    34. }
    35. time.Sleep(1 * time.Second)
    36. }