WaitGroup VS context.Context

  • 在使用WaitGroup值的时候,最好用”先统一Add,再并发Done,最后Wait”的标准模式来构建协作流程
  • 若在WaitGroup值在Wait方法的同时,为增大其计数器的值,而并发的调用该值的Add方法,那么很可能会引发panic
  • 如果WaitGroup一开始不确定执行子任务goroutine的数量,可以采用分批执行子任务的goroutine
  • 使用context.Background和context.WithCancel函数,并得到一个可撤销的context.Context类型的值,以及一个context.CancelFunc类型的撤销函数
  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "sync/atomic"
  6. "time"
  7. )
  8. func main() {
  9. coordinatWithContext()
  10. }
  11. func coordinatWithContext() {
  12. total := 12
  13. var num int32
  14. fmt.Printf("The number: %d [with context.Context]\n", num)
  15. cxt, cancelFunc := context.WithCancel(context.Background())
  16. for i := 1; i <= total; i ++ {
  17. go addNum(&num, i, func() {
  18. if atomic.LoadInt32(&num) == int32(total) {
  19. cancelFunc()
  20. }
  21. })
  22. }
  23. <-cxt.Done()
  24. fmt.Println("End ....")
  25. }
  26. func addNum(numP *int32, id int, deferFunc func()) {
  27. defer func() {
  28. deferFunc()
  29. }()
  30. for i := 0; ; i ++ {
  31. currNum := atomic.LoadInt32(numP)
  32. newNum := currNum + 1
  33. time.Sleep(time.Microsecond * 200)
  34. if atomic.CompareAndSwapInt32(numP, currNum, newNum){
  35. fmt.Printf("The number: %d [%d-%d]\n", newNum, id, i)
  36. break
  37. } else {
  38. fmt.Printf("The CAS operation failed. [%d-%d]\n", id, i)
  39. }
  40. }
  41. }

context.Context 类型

  • Context 类型实际上是一个接口类型
  • context 包实现该接口的所有私有类型,都是基于某个数据类型的指针类型
  • Context 类型值提供一类代表上下文的值,此类值是并发安全的,也就是说可以传播给多个goroutine
  • Context 类型的值可以繁衍,子值可以携带父值的属性和数据
  • Context 值共同构成一颗代表上下文全貌的树形结构,这颗树的根节点可通过 context.Background函数获得

context包中Context 值函数

  • WithCancel: 产生一个可撤销的parent子值
  • WithDeadline:产生一个可定时撤销的parent子值
  • WithTimeout:产生一个可定时撤销的parent子值
  • WithValue:产生一个携带额外数据的parent子值

可撤销context.Context值的撤销流程

  • 调用撤销函数 cancelFunc触发撤销
  • 一旦撤销触发,撤销信号就立刻传递给context.Context类型值
  • <-cxt.Done() 感知撤销信号,实际上是
  • 上述信号的传递实际上通过struct{}类型值的通道