C++ 各类mutex和读写锁性能比较

mutex和rwmutex类型基本概念

pthread_mutex_t:互斥锁,同一瞬间只能有一个线程能够获取锁,其他线程在等待获取锁的时候会进入休眠状态。因此pthread_mutex_t消耗的CPU资源很小,但是性能不高,因为会引起线程切换。

pthread_spinlock_t:自旋锁,同一瞬间也只能有一个线程能够获取锁,不同的是,其他线程在等待获取锁的过程中并不进入睡眠状态,而是在 CPU上进入“自旋”等待。自旋锁的性能很高,但是只适合对很小的代码段加锁(或短期持有的锁),自旋锁对CPU的占用相对较高。

pthread_rwlock_t:读写锁,同时可以有多个线程获得读锁,同时只允许有一个线程获得写锁。其他线程在等待锁的时候同样会进入睡眠。读写锁在互斥锁的基础上,允许多个线程“读”,通常在读多写少的场景下能提高性能。

std::mutex:互斥锁,同一瞬间只能有一个线程能够获取锁,等待获取锁的线程将会被阻塞并处于等待(waiting)状态。

std::shared_mutex:共享读写锁,支持多个读线程同时访问,但只允许一个写线程访问。std::shared_mutex实现读写分离,读锁需要跟踪读者数量,锁定/解锁开销更大。写操作仍然是独占的,写锁性能与std::mutex相当。

nsync_mutex_t:google开源并发库nsync的互斥锁。

nsync_rwlock_t:google开源并发库nsync的读写锁,即write mode下使用lock,read mode下使用rlock。

附nsync库地址:GitHub - google/nsync: nsync is a C library that exports various synchronization primitives, such as mutexesnsync is a C library that exports various synchronization primitives, such as mutexes - GitHub - google/nsync: nsync is a C library that exports various synchronization primitives, such as mutexeshttps://github.com/google/nsync

 测试机器情况

测试结果

本次测试使用std::unorder_map。

exp.1

os: linux

threads: 3读1写

hashmap length: 36675

read times: 10000 / thread

write times: 10000 / thread

epoch: 4

加锁方式

cost1

cost2

cost3

cost4

avg

std::mutex

9186 us

6446 us

6415 us

6403 us

7112 us

pthread_mutex_t

24062 us

19120 us

16866 us

17141 us

19297 us

9730 us

18748 us

16933 us

17394 us

15701 us

pthread_rwlock_t

21546 us

19722 us

18900 us

20660 us

20207 us

13019 us

10260 us

10149 us

10330 us

10939 us

pthread_spinlock_t

24909 us

24051 us

20031 us

20241 us

22308 us

10846 us

6420 us

6395 us

6393 us

7513 us

nsync_mutex_t

29628 us

28371 us

25245 us

25253 us

27124 us

9267 us

6562 us

6501 us

6500 us

7207 us

nsync_rwlock_t

(写lock, 读rlock mix)

25121 us

24177 us

21694 us

21674 us

23166 us

13293 us

10319 us

10439 us

10589 us

11160 us

std::shared_mutex

13134 us

13138 us

10487 us

10671 us

11857 us

exp.2

os: linux

threads: 3读1写

hashmap length: 36675

read times: 10000 / thread

write times: 1000 / thread

epoch: 4

加锁方式

cost1

cost2

cost3

cost4

avg

std::mutex

4965 us

4612 us

4630 us

4646 us

4713 us

pthread_mutex_t

12305 us

9831 us

10552 us

10900 us

10897 us

4968 us

9776 us

10180 us

10894 us

8954 us

pthread_rwlock_t

13780 us

13561 us

13517 us

13342 us

13550 us

8906 us

8408 us

8443 us

8428 us

8546 us

pthread_spinlock_t

13822 us

12730 us

14003 us

13518 us

13518 us

5214 us

4691 us

4666 us

4754 us

4831 us

nsync_mutex_t

18121 us

17780 us

18220 us

18829 us

18237 us

4699 us

4534 us

4496 us

4675 us

4601 us

nsync_rwlock_t

(写lock, 读rlock mix)

16456 us

16375 us

15957 us

16975 us

16440 us

8821 us

8800 us

8592 us

8473 us

8671 us

std::shared_mutex

8891 us

8565 us

8748 us

8559 us

8690 us

exp.3

os: linux

threads: 4读2写

hashmap length: 36675

read times: 10000 / thread

write times: 1000 / thread

epoch: 4

加锁方式

cost1

cost2

cost3

cost4

avg

std::mutex

7697 us

6348 us

6094 us

6086 us

6556 us

pthread_mutex_t

21670 us

18028 us

18288 us

18539 us

19131 us

6684 us

18915 us

18855 us

18791 us

15811 us

pthread_rwlock_t

20989 us

20792 us

20321 us

20618 us

20680 us

16854 us

16228 us

15962 us

16417 us

16365 us

pthread_spinlock_t

21585 us

22893 us

21294 us

22650 us

22105 us

7055 us

7292 us

6142 us

7366 us

6963 us

nsync_mutex_t

24586 us

24548 us

24948 us

25607 us

24922 us

6602 us

6008 us

5980 us

5985 us

6143 us

nsync_rwlock_t

(写lock, 读rlock mix)

22541 us

23444 us

22741 us

22859 us

22896 us

17076 us

15687 us

16011 us

15786 us

16140 us

std::shared_mutex

16847 us

16008 us

15922 us

16075 us

16213 us

exp.4

os: linux

threads: 6读3写

hashmap length: 36675

read times: 10000 / thread

write times: 3000 / thread

epoch: 4

加锁方式

cost1

cost2

cost3

cost4

avg

std::mutex

13420 us

10872 us

10700 us

10704 us

11424 us

pthread_mutex_t

43406 us

41285 us

40320 us

40768 us

41444 us

12956 us

40143 us

38579 us

38886 us

32641 us

pthread_rwlock_t

42786 us

38810 us

39713 us

38104 us

39853 us

31753 us

29010 us

29338 us

29371 us

29868 us

pthread_spinlock_t

49577 us

44320 us

42921 us

39853 us

44167 us

16009 us

12867 us

15400 us

10944 us

13805 us

nsync_mutex_t

48348 us

47371 us

44357 us

44867 us

46235 us

13579 us

10974 us

10890 us

10833 us

11569 us

nsync_rwlock_t

(写lock, 读rlock mix)

46518 us

46523 us

45031 us

44671 us

45685 us

32345 us

29480 us

29459 us

29425 us

30177 us

std::shared_mutex

31113 us

28986 us

28955 us

31712 us

30191 us

exp.5

os: linux

threads: 8读4写

hashmap length: 36675

read times: 10000 / thread

write times: 3000 / thread

epoch: 4

加锁方式

cost1

cost2

cost3

cost4

avg

std::mutex

18684 us

15223 us

15186 us

15191 us

16071 us

pthread_mutex_t

61733 us

55128 us

52081 us

52337 us

55319 us

18807 us

53878 us

51183 us

50275 us

43535 us

pthread_rwlock_t

58043 us

55728 us

54218 us

55093 us

55770 us

47133 us

44446 us

47107 us

43977 us

45665 us

pthread_spinlock_t

73558 us

65183 us

58573 us

61395 us

64677 us

20976 us

20723 us

17269 us

16682 us

18542 us

nsync_mutex_t

64249 us

61961 us

59575 us

60961 us

61686 us

19168 us

15756 us

15542 us

15591 us

16514 us

nsync_rwlock_t

(写lock, 读rlock mix)

66496 us

63230 us

59097 us

60990 us

62453 us

48564 us

45194 us

45920 us

45330 us

46252 us

std::shared_mutex

47652 us

44839 us

44833 us

44265 us

45397 us

实验结论 

1.按照keys的批次加锁的情况来看,pthread_mutex_t是性能最差的(剔除异常的cost1数据),其次由于共享资源的锁竞争较小,pthread_rwlock_t和std::shared_mutex相差不大表现最好的是nsync_mutex_t。就目前的实验参数来看,std::mutex和nsync_mutex_t还要略优于pthread_spinlock_t。nsync_rwlock_t、std::shared_mutex和pthread_rwlock_t在批量加锁的性能表现上是十分相近的。建议在一般情况下,使用std::mutex即可,尽量不用读写锁

2.pthread_mutex_t、pthread_rwlock_t、pthread_spinlock_t、nsync_mutex_t、nsync_rwlock_t在对单key加锁时性能普遍较低,不推荐在单key的粒度下使用。

3.当共享资源的锁竞争很小时,rwlock的cpu性能消耗反而比spinlock要高。所以不是所有读多写少的情况下,rwlock都优于spinlock

3.1分析:从spinlock和rwlock的功能上分析,前者只允许一个线程访问共享资源,其余线程均是忙等待;后者则允许多个读者同时访问,绝对限制只有一个线程可以作为写者。当读者访问资源时,写者必须忙等待,反之亦然。只从功能描述上分析,rwlock从功能上应该比spinlock性能要好。而cache数据结构一般使用hash表维护,当散列函数比较理想时,锁竞争发生的概率可能很小。由此猜测rwlock造成的性能下降,可能是因为rwlock的自身上锁解锁的cpu消耗要比spinlock高导致的。在上锁时,pthread_rwlock_wrlock和pthread_rwlock_rdlock会引入线程阻塞和切换的开销,而pthread_spin_lock会自旋等待锁,不会阻塞线程,减少了线程切换的开销。

4.在pthread系列中,读写锁的性能要优于互斥锁;但在nsync系列中,互斥锁要优于读写锁。

5.std::mutex在linux环境下性能比pthread_mutex_t要好的原因有以下几点:

  • std::mutex采用自旋锁+条件变量实现,在锁持有时间短的情况下可以避免上下文切换。而pthread_mutex_t直接使用操作系统mutex,依赖系统调用获取锁,上下文切换开销较大。

解释:GCC标准库实现的std::mutex,在Linux下默认会使用PTHREAD_MUTEX_ADAPTIVE_NP类型的pthread mutex作为底层实现。这种底层实现可以让std::mutex在不同的并发场景下自动选择最优的实现策略(锁竞争少时采用自旋,多时采用阻塞实现来避免频繁上下文切换)。而pthread_mutex_t是固定使用一种类型(阻塞),因此这是std::mutex在linux环境下比pthread_mutex_t更优的原因。

  • C++标准库mutex实现经过了大量优化,如GCC里采用了特定于CPU的优化指令。

  • std::mutex可以实现锁粒度更细的优化控制,避免不必要的锁竞争。

  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
C++中的读写锁(ReadWrite Lock)是一种线程同步机制,用于在多线程环境下实现对共享资源的读写操作。它允许多个线程同时读取共享资源,但只允许一个线程进行写操作。 在C++中,读写锁的实现通常使用std::shared_mutex类(也称为shared_mutex)来实现。它是C++14标准引入的一个新特性,提供了读写锁的功能。 使用std::shared_mutex时,可以使用两种不同的锁来进行操作: 1. std::shared_lock:用于共享资源的读取操作。多个线程可以同时获取共享锁,并且不会阻塞彼此。 2. std::unique_lock:用于独占资源的写入操作。只允许一个线程获取独占锁,并且其他线程无法获取共享锁。 通过使用这两种不同的锁,可以实现对共享资源的读写操作的并发性。 下面是一个简单的示例代码,演示了如何使用std::shared_mutex实现读写锁: ```cpp #include <iostream> #include <shared_mutex> #include <thread> std::shared_mutex rwMutex; int sharedData = 0; void ReadData() { std::shared_lock<std::shared_mutex> lock(rwMutex); std::cout << "Read data: " << sharedData << std::endl; } void WriteData() { std::unique_lock<std::shared_mutex> lock(rwMutex); sharedData++; std::cout << "Write data: " << sharedData << std::endl; } int main() { std::thread t1(ReadData); std::thread t2(ReadData); std::thread t3(WriteData); t1.join(); t2.join(); t3.join(); return 0; } ``` 在上述示例中,三个线程分别执行了读取操作和写入操作。使用std::shared_lock获取共享锁来实现读取操作,使用std::unique_lock获取独占锁来实现写入操作。这样可以保证在写入操作时不会有其他线程同时进行读取或写入操作。 需要注意的是,std::shared_mutexC++14引入的特性,因此在使用之前,请确保编译器支持C++14标准。如果编译器不支持C++14,也可以使用第三方库或自行实现读写锁

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值