go---context.Context 类型

如果不能一开始就确定子任务的 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)
		}
	}
}

go---context.Context 类型_第1张图片
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()
}

go---context.Context 类型_第2张图片

你可能感兴趣的:(Go语言核心36讲(郝林)笔记)