1.协程对共享资源的竞争
一个公共资源的使用,依赖于哪个协程对其的竞争力大,就优先给谁使用
package main
import (
"fmt"
"sync"
)
var result int //共享资源
var wg sync.WaitGroup
func add() {
for i := 0; i < 10000; i++ {
result = result + 1
}
wg.Done()
}
func sub() {
for i := 0; i < 10000; i++ {
result = result - 1
}
wg.Done()
}
func main() {
wg.Add(2)
go add()
go sub()
wg.Wait()
fmt.Println(result)//简单理解来说,这里应该打印的是0
}
//8849
//5798
//...每次运行打印的都不一样
由于 go协程的高并发特性,所以使得add()与sub()协程被切换着运行,如果两个协程的竞争力完全相同,那么就成了result 加一,减一交替进行,但是如果竞争力一旦有不同,那就会出现,加一执行多次,然后才执行一次减一【或者说】减一执行多次,然后才执行一次加一,但是仔细思考一下会发现,即使是这样,大不了就是竞争力大的一方先达到加的次数,或者减的次数,然后剩下竞争力小的一方独自执行逻辑,但是这样也不影响最后的结果呀!
所以:为什么会出现对此运行的值不一样,而且还都不是 0 呢 ???
回答:加一和减一的核心代码语句不是原子性的操作
当加一和减一具有原子性的时候,就是左图一样,三个步骤一定是绑定在一起执行的
不具有原子性的时候,就是右图一样,具体到拿到值,加减操作,写入都是协程切换的单位
不难看出,一定会随意切换出执行步骤,比如右图加一,减一各执行一次后的结果为 -1
所以:解决方案就是从保证加减执行核心语句的原子性入手
2.互斥锁
保证原子性的操作,很容易想到的就是加锁
加锁的本质是让资源被锁住,谁锁的就只能让谁先享用资源,直到被释放
但由于从代码的侵入上来看,表现成了,给一段代码进行了加锁,放锁
所以很像是对【一段逻辑】实行了绑定,实行了原子性操作
var result int //共享资源
var wg sync.WaitGroup
var myLock sync.Mutex
func add() {
for i := 0; i < 10000; i++ {
myLock.Lock()
result = result + 1
myLock.Unlock()
}
wg.Done()
}
func sub() {
for i := 0; i < 10000; i++ {
myLock.Lock()
result = result - 1
myLock.Unlock()
}
wg.Done()
}
func main() {
wg.Add(2)
go add()
go sub()
wg.Wait()
fmt.Println(result)
}
//0
从加锁,放锁的代码就可以看出,用锁来保证原子性,特别具有自定义的性质,因为你完全可以按照自己的需求给任意的几行代码加上锁的限制,从而达到步骤绑定的效果。