WaitGroup VS context.Context
- 在使用WaitGroup值的时候,最好用”先统一Add,再并发Done,最后Wait”的标准模式来构建协作流程
- 若在WaitGroup值在Wait方法的同时,为增大其计数器的值,而并发的调用该值的Add方法,那么很可能会引发panic
- 如果WaitGroup一开始不确定执行子任务goroutine的数量,可以采用分批执行子任务的goroutine
- 使用context.Background和context.WithCancel函数,并得到一个可撤销的context.Context类型的值,以及一个context.CancelFunc类型的撤销函数
package main
import (
"context"
"fmt"
"sync/atomic"
"time"
)
func main() {
coordinatWithContext()
}
func coordinatWithContext() {
total := 12
var num int32
fmt.Printf("The number: %d [with context.Context]\n", num)
cxt, cancelFunc := context.WithCancel(context.Background())
for i := 1; i <= total; i ++ {
go addNum(&num, i, func() {
if atomic.LoadInt32(&num) == int32(total) {
cancelFunc()
}
})
}
<-cxt.Done()
fmt.Println("End ....")
}
func addNum(numP *int32, id int, deferFunc func()) {
defer func() {
deferFunc()
}()
for i := 0; ; i ++ {
currNum := atomic.LoadInt32(numP)
newNum := currNum + 1
time.Sleep(time.Microsecond * 200)
if atomic.CompareAndSwapInt32(numP, currNum, newNum){
fmt.Printf("The number: %d [%d-%d]\n", newNum, id, i)
break
} else {
fmt.Printf("The CAS operation failed. [%d-%d]\n", id, i)
}
}
}
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{}类型值的通道