在前面的三讲中我们使用的mutex都是普通的std::mutex,这里介绍一下shared_mutex,版本为C++17
std::shared_mutex
的底层实现时操作系统提供的读写锁,在读多写少的情况下,该shared_mutex
比mutex
更加高效。
它提供了常用的四种方法:
lock
和unlock
分别用于获取写锁和解除写锁
lock_shared
和unlock_shared
分别用于获取读锁和解除读锁
写锁模式称为排他锁,读锁模式称为共享锁。
c++11和c++14标准中分别引入unique_lock
和shared_lock
两个类模板配合shared_mutex
使用。
对象在构造时自动对std::shared_mutex
加锁,析构时自动对其解锁。
前者用于加解写锁,后者用于加解读锁。
当然在第二讲中,我们也谈到了unique_lock
对于普通锁mutex的一种应用,当时是和std::lock_guard
对比的。
下面是案例代码,对于共享资源我们创建多个读线程和一个写线程,分别使用std::mutex
和std::shared_mutex
做一下性能测试。
#include <iostream>
#include <thread>
#include <mutex>
#include <shared_mutex>
// 读线程数量
#define READER_THREAD_COUNT 4
// 最大循环次数
#define LOOP_COUNT 500000
class shared_mutex_counter {
// 这里使用的锁是 shared_mutex
private:
mutable std::shared_mutex m_mutex;
unsigned int m_value = 0; // 多个线程共享的资源
public:
shared_mutex_counter() = default;
~shared_mutex_counter() = default;
// shared_lock 同一时可多个读线程可以同时访问m_value的值
unsigned int get() const
{
// 这里使用shared_lock
std::shared_lock<std::shared_mutex> lock(m_mutex);
return m_value;
}
// unique_lock 同一时间仅有一个写线程可以修改m_value的值
void incremet() {
std::unique_lock<std::shared_mutex> lock(m_mutex);
m_value++;
}
};
class mutex_counter {
// 这里使用的锁是 shared_mutex
private:
mutable std::mutex m_mutex;
unsigned int m_value = 0; // 多个线程共享的资源
public:
mutex_counter() = default;
~mutex_counter() = default;
// 同一时间仅有一个读线程可以同时访问m_value的值
unsigned int get() const
{
std::unique_lock<std::mutex> lock(m_mutex);
return m_value;
}
// 同一时间仅有一个写线程可以修改m_value的值
void incremet()
{
std::unique_lock<std::mutex> lock(m_mutex);
m_value++;
}
};
// 测试 shared_mutex
void test_shared_mutex()
{
shared_mutex_counter counter;
unsigned int temp;
// 写线程函数
auto write = [&counter]() {
for (int i = 0; i < LOOP_COUNT; i++) {
counter.incremet();
}
};
// 读线程函数
auto read = [&counter, &temp]() {
for (int i = 0; i < LOOP_COUNT; i++) {
temp = counter.get();
}
};
// 存放读线程对象指针的数组
std::thread** tarray = new std::thread* [READER_THREAD_COUNT];
// 记录起始时间
clock_t start = clock();
// 创建 READER_THREAD_COUNT 个读线程
for (int i = 0; i < READER_THREAD_COUNT; i++) {
tarray[i] = new std::thread(read);
}
// 创建一个写线程
std::thread tw(write);
// 等待join
for (int i = 0; i < READER_THREAD_COUNT; i++) {
tarray[i]->join();
}
tw.join();
clock_t end = clock();
std::cout << "test shared_mutex" << std::endl;
std::cout << "thread count " << READER_THREAD_COUNT << std::endl;
std::cout << "spend time " << end - start << std::endl;
std::cout << "result : " << counter.get() << std::endl;
std::cout << "temp : " << temp << std::endl;
}
// 测试 mutex
void test_mutex()
{
mutex_counter counter;
unsigned int temp;
// 写线程函数
auto write = [&counter]() {
for (int i = 0; i < LOOP_COUNT; i++) {
counter.incremet();
}
};
// 读线程函数
auto read = [&counter, &temp]() {
for (int i = 0; i < LOOP_COUNT; i++) {
temp = counter.get();
}
};
// 存放读线程对象指针的数组
std::thread** tarray = new std::thread* [READER_THREAD_COUNT];
// 记录起始时间
clock_t start = clock();
// 创建 READER_THREAD_COUNT 个读线程
for (int i = 0; i < READER_THREAD_COUNT; i++) {
tarray[i] = new std::thread(read);
}
// 创建一个写线程
std::thread tw(write);
// 等待join
for (int i = 0; i < READER_THREAD_COUNT; i++) {
tarray[i]->join();
}
tw.join();
clock_t end = clock();
std::cout << "test mutex" << std::endl;
std::cout << "thread count " << READER_THREAD_COUNT << std::endl;
std::cout << "spend time " << end - start << std::endl;
std::cout << "result : " << counter.get() << std::endl;
std::cout << "temp : " << temp << std::endl;
}
int main() {
test_mutex();
std::cout << std::endl;
test_shared_mutex();
return 0;
}
测试结果如下:
test mutex
thread count 8
spend time 1312
result : 500000
temp : 500000
test shared_mutex
thread count 8
spend time 601
result : 500000
temp : 475766
有个问题,我同样程序在windows的clion和vs上跑结果差异较大,clion跑出来的结果shared_mutex耗时比mutex多好多。
linux下的vscode跑出来结果正常。