先来看这个例子
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
哦吼,售罄卖完了。。。