如果不能一开始就确定子任务的 goroutine 的数量,那么使用 WaitGroup 值是有风险的。
Context 类型可以提供一类代表上下文的值。此类值是并发安全的,也就是说可以被传播给多个 goroutine。
package main
import (
"context"
"fmt"
"sync/atomic"
"time"
)
func main() {
coordinateWithContext()
}
func coordinateWithContext() {
total := 3
var num int32
fmt.Printf("The number: %d [with context.Context]\n", num)
// context.Background() 返回一个空的 Context
// 这个空的 Context 一般用于整个 Context 树的根节点
// context.WithCancel(parent) 创建一个可取消的子 Context 和 context.CancelFunc 撤销函数
cxt, cancelFunc := context.WithCancel(context.Background())
for i := 0; i < total; i++ {
// addNum 的最后一个参数是一个匿名函数
go addNum(&num, i, func() {
if atomic.LoadInt32(&num) == int32(total) {
// 如果所有的 addNum 都执行完毕
// 就立即通知分发子任务的 goroutine
// cancelFunc() 负责触发信号,cxt 负责传达信号
cancelFunc()
}
})
}
// 试图针对该函数的返回通道进行接收操作
// Done() 会返回一个通道
// 一旦 cancelFunc() 被调用,该通道就会被关闭
// 以实现“等待所有的 addNum 函数都执行完毕”的功能
<- cxt.Done()
fmt.Println("End.")
}
// 原子地增加一次 numP 所指的变量的值
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.Millisecond * 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 值的函数,既:WithCancel、WithDeadline、WithTimeout 和 WithValue。
Context 类型的实际值分为三种:根 Context 值、可撤销的 Context 值和含数据的 Context 值。
所有的 Context 值共同构成了一棵全局上下文树。
可撤销的 Context 值分为:只可手动撤销的 Context 值和可以定时撤销的 Context 值。
撤销信号沿着上下文树叶子节点的方向传播。
含数据的 Context 值可以携带数据,但不能被撤销。
package main
import (
"context"
"fmt"
"time"
)
type myKey int
func main() {
keys := []myKey {
myKey(2),
myKey(3),
myKey(6),
}
values := []string{
"value in node2",
"value in node3",
"value in node6",
}
rootNode := context.Background()
node1, cancelFunc1 := context.WithCancel(rootNode)
defer cancelFunc1()
node2 := context.WithValue(node1, keys[0], values[0])
node3 := context.WithValue(node2, keys[1], values[1])
// 如果父值中未存储相等的键,则沿着上下文根节点的方向一路查找下去。
fmt.Printf("The value of the key %v found in the node3: %v\n", keys[0], node3.Value(keys[0]))
fmt.Printf("The value of the key %v found in the node3: %v\n", keys[1], node3.Value(keys[1]))
fmt.Printf("The value of the key %v found in the node3: %v\n", keys[2], node3.Value(keys[2]))
fmt.Println()
node4, _ := context.WithCancel(node3)
node5, _ := context.WithTimeout(node4, time.Hour)
fmt.Printf("The value of the key %v found in the node5: %v\n", keys[0], node5.Value(keys[0]))
fmt.Printf("The value of the key %v found in the node5: %v\n", keys[1], node5.Value(keys[1]))
fmt.Println()
}