go语言 cgo(《Go 语言并发之道》读书笔记(三))
今天这篇笔记我们来学习锁:互斥锁(Mutex) 和 读写锁(RWMutex)
互斥锁(Mutex)首先我们来看一段代码 ,没有加锁的情况下 ,两个goroutine同时修改一个变量 ,会发生什么
func main() { var count int increment := func() { count++ fmt.Printf(" Incrementing: %d \n", count) } decrement := func() { count-- fmt.Printf(" Decrementing: %d \n", count) } var arithmetic sync.WaitGroup for i := 0; i <= 5; i++ { arithmetic.Add(1) go func() { defer arithmetic.Done() increment() }() } for i := 0; i <= 5; i++ { arithmetic.Add(1) go func() { defer arithmetic.Done() decrement() }() } arithmetic.Wait() fmt.Println("Arithmetic complete.") }上面的代码定义了一个increment方法和一个decrement方法 ,他们都操作count变量 , 然后各自启动5个goutinue去调用这两个方法 。 结果如下所示
Decrementing: 1 Incrementing: 2 Incrementing: 1 Decrementing: 0 Decrementing: -1 Decrementing: -1 Incrementing: 0 Decrementing: -2 Decrementing: -3 Incrementing: -2 Incrementing: -1 Incrementing: 0 Arithmetic complete.我们可以看到 ,结果是乱的 ,第一个decrementing 应该是-1 ,结果这里输出了1 ,第三个incrementing应该是3 ,结果输出是1. 这样的效果肯定不是我们期望的 , 当多个goroutine共享一个变量的时候 ,我们需要加锁,保证一次只有一个goroutine能够拿到锁 。如下代码
func main() { var count int var lock sync.Mutex increment := func() { lock.Lock() defer lock.Unlock() count++ fmt.Printf(" Incrementing: %d \n", count) } decrement := func() { lock.Lock() defer lock.Unlock() count-- fmt.Printf(" Decrementing: %d \n", count) } var arithmetic sync.WaitGroup for i := 0; i <= 5; i++ { arithmetic.Add(1) go func() { defer arithmetic.Done() increment() }() } for i := 0; i <= 5; i++ { arithmetic.Add(1) go func() { defer arithmetic.Done() decrement() }() } arithmetic.Wait() fmt.Println("Arithmetic complete.") }我们在方法中加了lock.Lock()和defer lock.Unlock() , 运行的效果如下图
Incrementing: 1 Incrementing: 2 Incrementing: 3 Incrementing: 4 Incrementing: 5 Decrementing: 4 Decrementing: 3 Decrementing: 2 Decrementing: 1 Decrementing: 0 Decrementing: -1 Incrementing: 0这样的结果符合我们的预期 , incrementing的时候和上一条比加了1, decrementing的时候和上一条比减少了1 ,代码改动是有效的 ,这就是锁的作用 , 加锁后保证一次只有一个goroutine访问共享的变量 。
读写锁(RWMutex)什么是读写锁呢? 读写锁允许多个只读操作并行进行 ,而写操作会完全互斥 。 还是使用上面的例子 ,假如我有个方法只是想读取count的value ,并不改变它 ,那么我们就可以用RWMutex.
我们稍微改变下上面的代码 func main() { var count int var lock sync.RWMutex increment := func() { lock.Lock() defer lock.Unlock() count++ fmt.Printf(" Incrementing: %d \n", count) } decrement := func() { lock.Lock() defer lock.Unlock() count-- fmt.Printf(" Decrementing: %d %d\n", count, time.Now().Nanosecond()) time.Sleep(time.Second) } read := func() { lock.RLock() defer lock.RUnlock() fmt.Printf(" reading: %d %d\n", count, time.Now().Nanosecond()) time.Sleep(time.Second) } var arithmetic sync.WaitGroup for i := 0; i <= 5; i++ { arithmetic.Add(1) go func() { defer arithmetic.Done() increment() }() } for i := 0; i <= 5; i++ { arithmetic.Add(1) go func() { defer arithmetic.Done() decrement() }() } for i := 0; i <= 10; i++ { arithmetic.Add(1) go func() { defer arithmetic.Done() read() }() } arithmetic.Wait() fmt.Println("Arithmetic complete.") }两个改动 ,将Mutex换成RWMutex , 增加了一个read方法 ,它只读取count变量 ,它加锁的方法是lock.RLock(), 同时我们故意加了time.Sleep(time.Second) ,让read方法和decrement方法执行的时候,停顿一下 。
执行结果如下所示 Incrementing: 1 reading: 1 177230700 reading: 1 177230700 reading: 1 177230700 reading: 1 177230700 reading: 1 177230700 reading: 1 177230700 reading: 1 177230700 reading: 1 177230700 reading: 1 177230700 reading: 1 177230700 reading: 1 177230700 Incrementing: 2 Incrementing: 3 Incrementing: 4 Incrementing: 5 Decrementing: 4 193430000 Decrementing: 3 204475300 Decrementing: 2 213426500 Decrementing: 1 227715200 Decrementing: 0 240186800 Decrementing: -1 255299900 Incrementing: 0 Arithmetic complete.我们可以看到read方法 ,几个gorountine执行的时间几乎一样 ,他们都能拿到读锁,不会被阻塞 , 而且它拿到了一个准确的当时的value. 而decrement方法 ,相同的代码 ,我们使用的是写锁 ,不同的gorountine会锁住 ,他们的执行时间会相差 。 这就是读写锁 。
拿读锁我们还可以用RWMutex.RLocker()来拿到锁对象sync.Locker 。
书中还比较了一个RWMutex和Mutex的性能差异 ,
当Reader数量比较小(<8)时 ,RWMutex性能稍差
当Reader数量大于8小于65536的时候 , RWMutex比Mutex快一倍
当Reader数量大于131072的时候 ,RWMutex又比Mutex稍慢
作者说RWMutex要比Mutex稍复杂 ,所以会有这样的结果
作者在书中说:通常建议使用RWMutex ,而不是Mutex , 因为它在逻辑上更合理 。创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!