在多线程编程中,常常需要协调大量线程对共享数据的访问。当程序中存在数量占优的读线程时(例如,监控系统、数据缓存等),使用传统的std::mutex会迫使所有读线程串行化,形成不必要的性能瓶颈。C++17 引入的 std::shared_mutex 为此提供了更优的解决方案。它通过共享读与独占写的灵活模式,允许多个读线程并发执行,从而释放了巨大的性能潜力。
1. 问题场景
我们有一个全局变量 counter,100 个线程负责读取它的值,10 个线程负责写入,为了防止竞态,我们在读写时都加了 lock。
#if 1
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
#include <chrono>
std::mutex mtx;
int counter = 0;
const int READER_COUNT = 100;
const int WRITER_COUNT = 10;
void read_function()
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
mtx.lock();
int number = counter;
std::this_thread::sleep_for(std::chrono::milliseconds(2));
mtx.unlock();
}
void write_function()
{
std::this_thread::sleep_for(std::chrono::milliseconds(2));
mtx.lock();
++counter;
std::this_thread::sleep_for(std::chrono::milliseconds(5));
mtx.unlock();
}
void demo()
{
auto start_time = std::chrono::high_resolution_clock::now();
std::vector<std::thread> readers;
std::vector<std::thread> writers;
// 创建读线程
for (int i = 0; i < READER_COUNT; ++i)
{
readers.emplace_back(read_function);
}
// 创建写线程
for (int i = 0; i < WRITER_COUNT; ++i)
{
writers.emplace_back(write_function);
}
// 等待线程执行完毕
for (auto& reader : readers)
{
reader.join();
}
for (auto& writer : writers)
{
writer.join();
}
auto end_time = std::chrono::high_resolution_clock::now();
auto time = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
std::cout << "最终计数器值: " << counter << std::endl;
std::cout << "总执行时间: " << time.count() << " 毫秒" << std::endl;
std::cout << "总操作数量: " << (READER_COUNT + WRITER_COUNT) << " 次" << std::endl;
}
int main()
{
demo();
return 0;
}
#endif
最终计数器值: 10 总执行时间: 1840 毫秒 总操作数量: 110 次
虽然程序是正确的,数据也是安全的,但性能非常差。原因在于:
- 所有线程(包括只读线程)都必须等待锁
- 即使多个线程只是读取数据,也不能同时执行
- 造成了严重的串行化,100 个读线程几乎变成排队执行
这就导致了性能瓶颈,大量读线程的并发能力被锁机制白白浪费。
2. 解决思路
针对这种场景,C++17 引入了一个新的同步原语:std::shared_mutex(共享互斥量)它在语义上与普通 std::mutex 最大的区别是:
- 写锁(独占锁):同一时刻只能有一个线程持有
- 读锁(共享锁):多个线程可以同时持有
注意:写线程之间仍然互斥,即:读与读之间不冲突,读与写冲突,写与写冲突。
#if 1
#include <iostream>
#include <shared_mutex>
#include <thread>
#include <vector>
#include <chrono>
std::shared_mutex mtx;
int counter = 0;
const int READER_COUNT = 100;
const int WRITER_COUNT = 10;
void read_function()
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
mtx.lock_shared();
int number = counter;
std::this_thread::sleep_for(std::chrono::milliseconds(2));
mtx.unlock_shared();
}
void write_function()
{
std::this_thread::sleep_for(std::chrono::milliseconds(2));
mtx.lock();
++counter;
std::this_thread::sleep_for(std::chrono::milliseconds(5));
mtx.unlock();
}
void demo()
{
auto start_time = std::chrono::high_resolution_clock::now();
std::vector<std::thread> readers;
std::vector<std::thread> writers;
// 创建读线程
for (int i = 0; i < READER_COUNT; ++i)
{
readers.emplace_back(read_function);
}
// 创建写线程
for (int i = 0; i < WRITER_COUNT; ++i)
{
writers.emplace_back(write_function);
}
// 等待线程执行完毕
for (auto& reader : readers)
{
reader.join();
}
for (auto& writer : writers)
{
writer.join();
}
auto end_time = std::chrono::high_resolution_clock::now();
auto time = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
std::cout << "最终计数器值: " << counter << std::endl;
std::cout << "总执行时间: " << time.count() << " 毫秒" << std::endl;
std::cout << "总操作数量: " << (READER_COUNT + WRITER_COUNT) << " 次" << std::endl;
}
int main()
{
demo();
return 0;
}
#endif
最终计数器值: 10 总执行时间: 440 毫秒 总操作数量: 110 次
从结果可以看出:
- 数据正确性保持一致
- 执行时间从 1840ms 降到 440ms,性能提升接近 4 倍
- 主要原因是读线程得以并发执行,大大降低了锁竞争
3. 锁的开销
对于 std::shared_mutex 而言,其性能瓶颈与 std::mutex 类似:当线程等待锁时会阻塞,锁释放后需要唤醒等待线程,这一过程涉及用户态与内核态切换,以及寄存器状态的保存与恢复,单次开销较高。
std::shared_mutex 通过区分读操作和写操作,允许多个线程同时持有读锁,从而减少不必要的阻塞。为了实现这一点,std::shared_mutex 内部需要维护无锁、读锁和写锁等状态,并基于这些状态的进行较为复杂的加锁、解锁操作,因此,加锁或释放锁的基础开销通常比互斥锁更高。
在使用 std::shared_mutex 时,一方面会减少成本,一方面也会提高成本,所以,我们需要平衡以下两点:
- 加锁、解锁的基础开销
- 线程阻塞导致的上下文切换开销
当读操作占多数、读操作快速完成、线程竞争激烈时,std::shared_mutex 能够通过降低上下文切换开销体现性能优势。相反,如果写操作频繁、竞争不激烈,读写锁的额外基础开销可能抵消其优势,甚至比互斥锁更慢。
此外,std::shared_mutex 的使用复杂度也高于 std::mutex,需要正确管理 std::shared_mutex 的获取与释放。
因此,std::shared_mutex 的性能优势主要体现在读多写少、读操作快速完成、竞争激烈的场景下,而在其他情况下,互斥锁往往更加高效且实现简单。

冀公网安备13050302001966号