前言:

      测试golang锁的性能是个无聊的事情,说下缘由吧,前两天朋友问我如果要对一个slice进行线程安全的读写操作,需要读写锁还是互斥锁,哪个性能高一点? 

      Rwmutex在什么场景下会比mutex性能要好? 在我看来lock和unlock之间,在没有io逻辑,没有复杂的计算逻辑下,mutex互斥锁要比rwlock读写锁更加的高效。 像社区里rwlock读写锁的设计实现有好几种,大多是抽象两把lock和reader计数器实现。

   

     我对比过在cpp下lock和rwlock的性能对比,在简单赋值的逻辑下,他的benchmark跟我的预测是一样的。也就是说,互斥锁lock要比rwlock读写锁高效。当中间逻辑是一个空io读写操作时,rwlock也要比lock高效,这个也跟我们想的一样。 但当中间逻辑是map查找时,rwlock也要比lock高。但想来map是个复杂的数据结构,当查找key时,需要hashcode计算,然后通过hashcode找到数组里对应的bucket,然后再从链表里找到相关的key。

      比完c++的锁,那么我们对比下golang的sync.rwmutex和sync.mutex的性能。废话不多说,直接贴测试代码。

测试结果

测试了多协程使用mutex,rwmutex,在只读,只写,读写的测试场景。 貌似只有在只写的场景下,mutex要比rwmutex高一点。

把map的读写逻辑更换成全局的计数加减。可以发现跟上面的测试结果差不多,只写场景下mutex要比rwlock性能高一点。

sync.RwMutex源码

我们分析下golang sync.RwMutex的实现,他的结构里也是有读锁,写锁,reader计数器的,跟社区里的字段差不多。最大的区别是对于reader的计数使用atomic指令操作,而社区里reader的加减是通过拿互斥锁来实现的。

读锁的过程,  他是直接使用atomic来减法操作。当reader小于0,等待读锁。

释放读锁,也是使用atomic来对计数操作。当没有reader的时候,释放写锁。

写锁的过程,首先判断是否有读,有读,等待读来唤醒。释放写锁的时候,也会把读锁释放,释放了,自然就把Rlock的协程唤醒。

总结:

     没什么好总结的,锁竞争一直是高并发系统的一个问题。对于上面map + mutex的使用,我们可以用1.9之后的sync.Map替换。在读多写少下,sync.Map的性能要比sync.RwMutex + map高的多。大家可以看了sync.Map的实现原理后,会发现他的写性能不高,读是可以通过copy on write的方式无锁读,但是写操作还是会有锁的。我们可以使用类似java concurrentMap分段锁的方法来分解锁竞争的压力。

     解决锁竞争问题,除了上面的分段锁,还可以通过atomic cas指令来实现乐观锁。