三、《go 并发编程》临界资源安全问题

489 阅读2分钟
先来看这个例子
package main

import "fmt"

func main() {
	// 变量 a 的值为 3
	a := 3

	go func() {
		a = 2
		fmt.Println("子 goroutine 修改 a 的值为:",a)
	}()

	// 主 goroutine 修改 a 的值为 1
	a = 1
	fmt.Println("主 goroutine 修改 a 的值为:",a)
}

// 执行结果会有两种
// 第一种,主会抢占执行完,子被迫停止执行,返回 1
主 goroutine 修改 a 的值为: 1

// 第二种,主执行的同时,子也抢占到了资源,同时都执行
主 goroutine 修改 a 的值为: 1
子 goroutine 修改 a 的值为: 2
再来看我们让主睡眠延迟后执行
package main

import (
	"fmt"
	"time"
)

func main() {
	// 变量 a 的值为 3
	a := 3

	go func() {
		a = 2
		fmt.Println("子 goroutine 修改 a 的值为:",a)
	}()

	// 主 goroutine 修改 a 的值为 1
	a = 1
  // 睡眠等待 2s
	time.Sleep(2*time.Second)
	fmt.Println("主 goroutine 修改 a 的值为:",a)
  
  // 执行结果
  子 goroutine 修改 a 的值为: 2
  // 停顿 2s
	主 goroutine 修改 a 的值为: 2

过程分析

a 变量的值刚开始等于3,进入主 goroutine,值为1,但是此刻他睡眠了 然后资源会被子 goroutine 抢占使用并执行 a 的值变为 2,主 goroutine 的睡眠结束,继续执行下去,值最终就等于 2 这个时候,数据就显得非常不安全了

模拟电商某个产品售罄的场景
package main

import (
	"fmt"
	"math/rand"
	"time"
)

// 商品数量
var commodity int = 10

func main() {
	// 为了加快程序的响应,这里启动 4 个 goroutine 来处理卖的动作
	go buy("①")
	go buy("②")
	go buy("③")
	go buy("④")
	time.Sleep(3 * time.Second)
}

func buy(user string) {
	// 增加随机种子
	rand.Seed(time.Now().UnixNano())
	for {
		// 当商品数量足够时
		if commodity > 0 {
			// 这里模拟购买的处理时间,毕竟真实的场景受到服务器资源、IO处理、网络因素的影响
			// 处理的时长都不会完全一致
			time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond)
			commodity--
			fmt.Println(user, "窗口卖走1个,剩余:", commodity)
		} else {
			fmt.Println("哦吼,售罄卖完了。。。")
			// 结束循环
			break
		}
	}
}
来看看执行的结果
① 窗口卖走1个,剩余: 9
③ 窗口卖走1个,剩余: 8
② 窗口卖走1个,剩余: 6
① 窗口卖走1个,剩余: 7
④ 窗口卖走1个,剩余: 5
② 窗口卖走1个,剩余: 4
① 窗口卖走1个,剩余: 3
④ 窗口卖走1个,剩余: 2
③ 窗口卖走1个,剩余: 1
④ 窗口卖走1个,剩余: 0
哦吼,售罄卖完了。。。
② 窗口卖走1个,剩余: -1
哦吼,售罄卖完了。。。
① 窗口卖走1个,剩余: -2
哦吼,售罄卖完了。。。
③ 窗口卖走1个,剩余: -3
哦吼,售罄卖完了。。。

分析

image-20200924114445665