说明

C++14中引入std::shared_mutex
std::shared_mutex用于管理可转移和共享所有权的互斥对象,适用场景比较特殊:一个或多个读线程同时读取共享资源,且只有一个写线程来修改这个资源,这种情况下才能从shared_mutex获取性能优势。

  • shared_lock是read lock。搭配std::shared_mutex使用,被锁后仍允许其他线程执行同样被shared_lock的代码。
//shared_lock构造函数中调用了shared_mutex的lock_shared函数
    explicit shared_lock(mutex_type& _Mtx)
        : _Pmtx(_STD addressof(_Mtx)), _Owns(true) { // construct with mutex and lock shared
        _Mtx.lock_shared();
    }
  • lock_guard和unique_lock是write lock。被锁后不允许其他线程执行被shared_lock或unique_lock的代码。
//lock_guard以及unique_lock构造函数中调用了shared_mutex的lock函数
    explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) { // construct and lock
        _MyMutex.lock();
    }
//unique_lock
    explicit unique_lock(_Mutex& _Mtx) : _Pmtx(_STD addressof(_Mtx)), _Owns(false) { // construct and lock
        _Pmtx->lock();
        _Owns = true;
    }
  • std::shared_mutex 相比于mutex多提供了一个lock_shared函数,用于读锁。

测试

#include <shared_mutex>
#define READ_THREAD_COUNT 8  
#define LOOP_COUNT 5000000  
class TimeConsum
{
public:
	TimeConsum() { m_nTimeStart = std::chrono::high_resolution_clock::now(); };
	operator double&& () const noexcept
	{
		auto end = std::chrono::high_resolution_clock::now();
		return std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(end - m_nTimeStart).count();
	}

private:
	std::chrono::high_resolution_clock::time_point m_nTimeStart;
};

using  ReadLock	= std::shared_lock<std::shared_mutex> ;
using  WriteLock = std::lock_guard<std::shared_mutex>  ;
using  NormalLock = std::lock_guard<std::mutex>;

class SharedMutexCounter 
{
public:
	SharedMutexCounter() = default;

	unsigned int get() const 
	{
		//被mutable修饰的变量即使在const函数中也可以修改
		ReadLock lock(mutex);
		return value;
	}

	void increment() 
	{
		WriteLock lock(mutex);
		value++;
	}

	std::string name()const
	{
		return "shared";
	}

private:
	//在C++中,mutable也是为了突破const的限制而设置的。
	//被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中。
	mutable std::shared_mutex mutex;
	unsigned int value = 0;
};

class MutexCounter 
{
public:
	MutexCounter() = default;

	unsigned int get() const 
	{
		NormalLock lock(mutex);
		return value;
	}

	void increment() 
	{
		NormalLock lock(mutex);
		value++;
	}

	std::string name()const
	{
		return "normal";
	}

private:
	mutable std::mutex mutex;
	unsigned int value = 0;
};

template <typename COUNTER>
void test_mutex()
{
	TimeConsum cons;
	std::atomic_int32_t temp{ 0 };
	COUNTER counter;

	const auto writer = [&counter]() {for (int i = 0; i < LOOP_COUNT; ++i)counter.increment(); };
	const auto reader = [&counter, &temp]() {for (int i = 0; i < LOOP_COUNT; ++i)temp = counter.get(); };
	std::vector<std::thread>readlist;
	for (int i = 0; i < READ_THREAD_COUNT; ++i)
	{
		readlist.emplace_back(reader);
	}
	thread wr(writer);
	for (int i = 0; i < readlist.size(); ++i)
	{
		readlist[i].join();
	}
	wr.join();

	std::cout << "Test "<< counter.name()<<", count:[" << counter.get() << "]" << ",get:[" << temp << "]" << std::endl;
	std::cout << "Const time:[" << cons << "]" << std::endl;
}

void shared_lock_test()
{
	test_mutex<MutexCounter>();
	test_mutex<SharedMutexCounter>();
}

//output
Test normal, count:[5000000],get:[5000000]
Const time:[3821.9]
Test shared, count:[5000000],get:[5000000]
Const time:[2274.59]

Q.E.D.