共享资源的保护:使用 RWMutex 的正确姿势
在多线程编程中,保证共享资源的一致性是一个非常重要的问题。为了解决这个问题,Go 语言提供了一种称为 RWMutex 的同步机制,它可以用来保护共享资源,在不同场景下,它可以用来读写锁,也可以用来普通的互斥锁。在本文中,我们将深入探讨 RWMutex 的原理、使用方法和注意事项。
一、RWMutex 的原理
RWMutex 的实现基于 sync.Mutex,它包含一个 Mutex 和两个计数器,分别用来记录 read 和 write 的数量。当一个 goroutine 试图获取 RWMutex 的 read 锁或 write 锁时,它首先需要获取 Mutex 的锁,然后检查当前 RWMutex 的状态,如果 RWMutex 处于 write 状态,则 goroutine 需要等待,否则它可以获得 read 或 write 锁。在 read 状态下,多个 goroutine 可以同时读取共享资源,而在 write 状态下,只有一个 goroutine 能够写入共享资源。
1.RLock/RUnlock 的实现
没有 writer 竞争或持有锁时,readerCount 和我们正常理解的 reader 的计数是一样的;
如果有 writer 竞争锁或者持有锁时,那么,readerCount 不仅仅承担着 reader 的计数功能,还能够标识当前是否有 writer 竞争或持有锁,在这种情况下,请求锁的 reader 的处理进入第 8 行,阻塞等待锁的释放。
调用 RUnlock 的时候,我们需要将 Reader 的计数减去 1(第 22 行),因为 reader 的数量减少了一个。但是,第 23 行的 Add 的返回值还有另外一个含义。
如果它是负值,就表示当前有 writer 竞争锁,在这种情况下,还会调用 rUnlockSlow 方法,检查是不是 reader 都释放读锁了,如果读锁都释放了,那么可以唤醒请求写锁的 writer 了。
当一个或者多个 reader 持有锁的时候,竞争锁的 writer 会等待这些 reader 释放完,才可能持有这把锁。
例如,在房地产行业中有条规矩叫做“买卖不破租赁”,意思是说,就算房东把房子卖了,新业主也不能把当前的租户赶走,而是要等到租约结束后,才能接管房子。这和 RWMutex 的设计是一样的。
当 writer 请求锁的时候,是无法改变既有的 reader 持有锁的现实的,也不会强制这些 reader 释放锁,它的优先权只是限定后来的 reader 不要和它抢。所以,rUnlockSlow 将持有锁的 reader 计数减少 1 的时候,会检查既有的 reader 是不是都已经释放了锁,如果都释放了锁,就会唤醒 writer,让 writer 持有锁。
2.Lock/Unlock 的实现
这段代码是 RWMutex 的 Lock 方法的实现。该方法用于获取写锁,如果锁已经被其他 goroutine 获取,则当前 goroutine 会被阻塞,直到锁可用。
具体来说,该方法首先会获取写锁,然后将读锁的数量减去最大值,并将结果与最大值相加,得到的结果表示当前有多少个读锁。接着,如果有读锁存在,当前 goroutine 会等待所有读锁结束。最后,如果获取写锁成功,该方法会返回。
该方法的实现是基于信号量的。当有多个 goroutine 等待读锁时,它们会在读锁上等待。当有一个 goroutine 等待写锁时,它会在写锁上等待。这种实现方式可以保证读写锁的公平性,避免写锁饥饿。
这段代码是 RWMutex 的 Unlock 方法的实现。该方法用于释放写锁,如果锁未被获取,则会触发运行时错误。
具体来说,该方法会将读锁的数量加上最大值,并检查结果是否超过最大值。如果超过最大值,则说明锁未被获取。否则,该方法会释放所有等待读锁的 goroutine,并释放写锁。在释放读锁时,该方法会依次释放所有等待读锁的 goroutine。这种实现方式可以保证读写锁的公平性,避免写锁饥饿。
需要注意的是,RWMutex 的锁不与特定的 goroutine 相关联。一个 goroutine 可以获取 RWMutex 的读锁或写锁,并安排另一个 goroutine 来释放它。
二、RWMutex 的使用方法
在使用 RWMutex 时,需要根据具体的场景选择不同的读写锁使用方式。以下是几个使用 RWMutex 的场景及相应的读写锁使用方式:
多个 goroutine 同时读取共享资源:在这种情况下,可以使用读锁来提高并发性能。多个 goroutine 可以同时获得读锁,但只有一个 goroutine 可以获得写锁。例如:
多个 goroutine 同时写入共享资源:在这种情况下,需要使用写锁来保证共享资源的一致性。只有一个 goroutine 可以获得写锁,其他 goroutine 需要等待。例如:
读写锁的使用频率相当:在这种情况下,可以根据具体的情况选择使用读锁或写锁。例如:
三、RWMutex 的注意事项
在使用 RWMutex 时,需要注意锁的粒度。锁的粒度过大会导致效率低下,锁的粒度过小会导致程序的正确性受到威胁。
在使用 RWMutex 时,需要注意锁的获取和释放顺序。如果不按照正确的顺序获取和释放锁,会导致死锁。
在使用 RWMutex 时,需要注意共享资源的一致性。如果共享资源的访问不当,会导致程序的正确性受到威胁。
四、总结
RWMutex 是 Go 语言提供的一种同步机制,它可以用来保护共享资源,在不同场景下,它可以用来读写锁,也可以用来普通的互斥锁。在实际使用 RWMutex 时,需要根据具体的场景选择不同的读写锁使用方式,以提高程序的并发性能和保证共享资源的一致性。同时,需要注意锁的粒度、获取和释放顺序以及共享资源的一致性等问题。
版权声明: 本文为 InfoQ 作者【Jack】的原创文章。
原文链接:【http://xie.infoq.cn/article/1ac3ae244f302ee2463a55b94】。文章转载请联系作者。
评论