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的优化指令。文章来源:https://www.toymoban.com/news/detail-782070.html
-
std::mutex可以实现锁粒度更细的优化控制,避免不必要的锁竞争。文章来源地址https://www.toymoban.com/news/detail-782070.html
到了这里,关于C++ 各类mutex和读写锁性能比较的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!