临界资源定义
并发环境中多个进程、线程、协程共享的资源
临界资源的特点
可能会因为并发操作导致数据出现不一致性,举个栗子,下面代码中的a及时临界资源
package main
import (
\"fmt\"
\"time\"
)
func main() {
//临界资源
a := 1
go func() {
a = 2
fmt.Println(\"in this goroutine: a is : \", a)
}()
//在主goroutine中
a = 3
time.Sleep(1)
fmt.Println(\"in the main goroutine: a is : \", a)
}
经典的售票问题,好多个窗口同时售票的门票的数量就是一个典型的临界资源安全问题,下面用4个协程模拟一下售票过程:
package main
import (
\"fmt\"
\"math/rand\"
\"time\"
)
var ticket = 10 //the amount of the total ticket is 100
func main() {
//这里启动4个goroutine模拟4个售票口 同时售票
go saleTickets(\"ticket window1\")
go saleTickets(\"ticket window2\")
go saleTickets(\"ticket window3\")
go saleTickets(\"ticket window4\")
//这里为了保证 主协程 最后执行完 先用sleep (当然,也可以用同步等待组、chanel实现)
time.Sleep(10 * time.Second)
}
func saleTickets(name string) {
rand.Seed(time.Now().UnixNano())
for {
if ticket > 0 {
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
fmt.Println(name, \"saled: \", ticket)
ticket--
} else {
fmt.Println(name, \"sorry tickets are sold out\")
break
}
}
}
运行结果如下:
ticket window3 saled: 10
ticket window1 saled: 9
ticket window4 saled: 8
ticket window2 saled: 7
ticket window3 saled: 6
ticket window2 saled: 5
ticket window1 saled: 4
ticket window1 saled: 3
ticket window4 saled: 2
ticket window2 saled: 1
ticket window2 sorry tickets are sold out
ticket window4 saled: 0
ticket window4 sorry tickets are sold out
ticket window3 saled: -1
ticket window3 sorry tickets are sold out
ticket window1 saled: -2
ticket window1 sorry tickets are sold out
这里居然卖出了-2张票,这明显除了问题,问题出在哪里呢?
- 我们假设现在只剩下最后1张票了,现在程序在主协程里面
- 窗口4的协程拿到cpu资源,读取了剩余ticket总数为1,然后sleep,释放cpu资源
- 窗口3的协程拿到cpu资源,发现剩余ticket总数为1(因为窗口4的协程进入sleep了,并没有在窗口3拿到cpu资源之前对ticket进行修改),然后sleep,释放cpu资源。
- 窗口4醒了,ticket = 1 - 1 = 0
- 窗口3醒了,ticket = 0 - 1 = -1
针对这种问题,可以通过上锁,在某一时间段只允许一个goroutine来访问这个共享数据,访问完毕,解锁之后,其他goroutine才能访问的方式解决。
不过有意思的是,go并不鼓励这样以共享的方式去通信,而是以通信的方式去共享【也就是不鼓励用sync包上锁,鼓励使用chanel】
资源参考:bilibili