go中mutex的使用
假如说现在有一个公共的变量var count int
同时有两个方法Add() 和 Sub() Add方法可以将count加10000次,Sub方法可以将count减10000次。
现在同时启动两个goroutine来运行这两个方法例如:
var count int
var wg sync.WaitGroup
func add() {
defer wg.Done()
for range 100000 {
count += 1
}
}
func sub() {
defer wg.Done()
for range 100000 {
count -= 1
}
}
func main() {
wg.Add(2)
go add()
go sub()
wg.Wait()
fmt.Println(count)
}
运行之后会发现count的值不是预期中的零,而是每次运行之后都是一个随机的值。
这是因为事实上对count进行加1的操作并不是一个原子操作,而是可以分为3各部分:
加载count -> count + 1 -> 写入count
同理,对count减一也是三个部分:
加载count -> count - 1 -> 写入count
而在两个并发的过程当中,这六个独立的操作是可以不按照既定的顺序进行,每次执行的顺序都不唯一,会相互之间有交叉,所以这就造成了,我们每次运行之后count的结果为一个随机值的原因。
为了解决这种对公共资源的竞争情况,可以使用互斥锁,也就是 mutex
互斥锁可以让一个过程变为原子操作,即对于上面让count加1这个操作,如果这个 count += 1
被锁包裹,那么这个代码所表示的三个部分会变为一个整体。当然这是对于加了同一个锁的多个协程而言的。
比如如果有两个互斥锁lock1和lock2,在函数add()中 count += 1
被lock1包裹,而在函数sub()中 count -= 1
被lock2包裹,此时再运行仍然会打印出随机数。因此在使用互斥锁时,对于多个想要使用共同的公共资源的协程来说需要加上同一把锁。
例如:
var count int
var wg sync.WaitGroup
var lock sync.Mutex
func add() {
defer wg.Done()
for range 100000 {
lock.Lock()
count += 1
lock.Unlock()
}
}
func sub() {
defer wg.Done()
for range 100000 {
lock.Lock()
count -= 1
lock.Unlock()
}
}
func main() {
wg.Add(2)
go add()
go sub()
wg.Wait()
fmt.Println(count)
}
运行结果为:
0
而对于使用不同的锁的情况例如:
var (
count int
wg sync.WaitGroup
lock sync.Mutex
lock1 sync.Mutex
)
func add() {
defer wg.Done()
for range 100000 {
lock.Lock()
count += 1
lock.Unlock()
}
}
func sub() {
defer wg.Done()
for range 100000 {
lock1.Lock()
count -= 1
lock1.Unlock()
}
}
func main() {
wg.Add(2)
go add()
go sub()
wg.Wait()
fmt.Println(count)
}
运行结果仍然为一个随机数