内容简介:本系列是阅读 “The Go Programming Language” 理解和记录。在上篇中这样带来什么问题呢?假设有一个场景,用户需要频繁的查询 balance,这会导致 lock 被频繁的调用,不但
本系列是阅读 “The Go Programming Language” 理解和记录。
在上篇中 并发 Go 程序中的共享变量 (二):锁 我们的获取 balance 的方法也用了锁:
func Balance()int { mu.Lock() defer mu.Unlock() return balance }
这样带来什么问题呢?假设有一个场景,用户需要频繁的查询 balance,这会导致 lock 被频繁的调用,不但 Balance
函数彼此之间需要等待对方锁的释放,同时也会影响到 Widthdraw
和 Deposit
函数的调用:
package main import ( "fmt" "sync" "time" ) var ( mu sync.Mutex balance int ) func Balance()int { fmt.Println("Balance wait for another goroutine release lock") mu.Lock() fmt.Println("Balance acquired lock") defer mu.Unlock() return balance } func Balance2()int { mu.Lock() fmt.Println("Balance2 acquired lock") defer mu.Unlock() time.Sleep(10 * time.Second) fmt.Println("Balance2 release lock") return balance } func Deposit(amountint) { mu.Lock() deposit(amount) mu.Unlock() } func Withdraw(amountint)bool { mu.Lock() defer mu.Unlock() deposit(-amount) if balance < 0 { deposit(amount) return false // insufficient funds } return true } func deposit(amountint) { balance = balance + amount } func main() { balance = 100 wait := make(chan int) go func() { fmt.Println("Balance2 == >", Balance2()) wait <- 1 }() time.Sleep(2 * time.Second) // 此处 sleep 操作是为了 Balance2 优先获得执行 fmt.Println("Balance ==>", Balance()) <-wait }
为了演示方便,代码中构造了两个读取 balance 的函数 Balance
和 Balance2
,假设在 Balance2
由于某种原因读取 balance 的操作需要等待一段时间,这个时候如果 Balance2
不结束 Balance
就无法执行,输出结果如下:
Balance2 acquired lock Balance wait for another goroutine release lock Balance2 release lock Balance2 == > 100 Balance acquired lock Balance ==> 100
由输出结果不难看出(程序中的 IO 操作能够引起 goroutine 执行的切换,所以需要小心对待 print 才能正确演示我们要的结果),Balance 必须要等到 Balance2 释放锁之后才能获取 balance,程序的整个执行过程中并没有修改 balance 的操作,也就是如果仅仅只有读取 balance 的操作,它们的并发执行是安全的,但是由于 sync.Mutex
的使用,这将导致这种并发安全的操作也会带来不必要的性能损耗: 锁的频繁获取和释放
。这里如果有一种特殊的锁能够允许对 balance 的读取操作可以并行执行,但是一旦遇到修改操作就必须要等待锁的获取才能继续读取,这部分的性能损耗就可以弥补。幸运地是,Go 提供了这种锁,称之为 multiple readers,single writer
锁: sync.RWMutex
。
我们队上面的代码进行小小的修改,替换获取锁和释放锁的代码:
var ( mu sync.RWMutex balance int ) func Balance()int { fmt.Println("Balance wait for another goroutine release lock") mu.RLock() // 修改处 fmt.Println("Balance acquired lock") defer mu.RUnlock() // 修改处 return balance } func Balance2()int { mu.RLock() // 修改处 fmt.Println("Balance2 acquired lock") defer mu.RUnlock() // 修改处 time.Sleep(10 * time.Second) fmt.Println("Balance2 release lock") return balance }
执行输出如下:
Balance2 acquired lock Balance wait for another goroutine release lock Balance acquired lock Balance ==> 100 Balance2 release lock Balance2 == > 100
可以看到即使 Balance2 没有释放锁,Balance 依然可以获得锁,程序整体的执行效率提升了,尤其是读越多,效果越显著。
使用了 RLock 的 Balance 依然在遇到 Widthdraw 等已经通过 Lock 获取锁的 函数执行时必须要继续等待:
package main import ( "fmt" "sync" "time" ) var ( mu sync.RWMutex balance int ) func Balance()int { fmt.Println("Balance wait for another goroutine release lock") mu.RLock() fmt.Println("Balance acquired lock") defer mu.RUnlock() return balance } func Deposit(amountint) { mu.Lock() fmt.Println("Deposit acquired lock") defer mu.Unlock() time.Sleep(5 * time.Second) deposit(amount) } func Withdraw(amountint)bool { mu.Lock() defer mu.Unlock() deposit(-amount) if balance < 0 { deposit(amount) return false // insufficient funds } return true } func deposit(amountint) { balance = balance + amount } func main() { balance = 100 wait := make(chan int) go func() { Deposit(100) wait <- 1 }() time.Sleep(1 * time.Second) fmt.Println("Balance ==>", Balance()) <-wait }
即使使用 RLock,Balance 函数还是需要等待 Deposit 释放锁,说明我们的目的达到了: 多读并行,一写排它 。
sync.RWMutex
提供的 RLock 只能用于 critical section 没有对 shared variable 进行写的情况,但是记住要始终谨慎对待,因为有很多隐式的对 shared variable 的修改不是很容易察觉,比如其它调用函数的读取计数器等。
以上所述就是小编给大家介绍的《并发 Go 程序中的共享变量 (三):读写锁》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Golang并发:一招掌握无阻塞通道读写
- golang并发编程读写锁sync.RWMutex
- Golang 并发编程之互斥锁、读写锁详解
- Java 多线程并发读写锁 ReadWriteLock 实现原理剖析
- Java并发编程笔记之读写锁 ReentrantReadWriteLock 1.8 原理
- HDFS NameNode 高并发数据读写架构及QJM选举深入研究
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JavaScript语言精髓与编程实践
周爱民 / 电子工业出版社 / 2012-3 / 79.00元
《JavaScript语言精髓与编程实践(第2版)》详细讲述JavaScript作为一种混合式语言的各方面特性,包括过程式、面向对象、函数式和动态语言特性等,在动态函数式语言特性方面有着尤为细致的讲述。《JavaScript语言精髓与编程实践(第2版)》的主要努力之一,就是分解出这些语言原子,并重现将它们混合在一起的过程与方法。通过从复杂性到单一语言特性的还原过程,读者可了解到语言的本质,以及“层......一起来看看 《JavaScript语言精髓与编程实践》 这本书的介绍吧!