STL 和 Boost 都提供了 shared_mutex
来解决「读者-写者」问题。shared_mutex
这个名字并不十分贴切,不如 pthread 直呼「读写锁」。函数
所谓「读写锁」,就是同时能够被多个读者拥有,可是只能被一个写者拥有的锁。而所谓「多个读者、单个写者」,并不是指程序中只有一个写者(线程),而是说不能有多个写者同时去写。测试
下面看一个计数器的例子。ui
class Counter { public: Counter() : value_(0) { } // Multiple threads/readers can read the counter's value at the same time. std::size_t Get() const { std::shared_lock<std::shared_mutex> lock(mutex_); return value_; } // Only one thread/writer can increment/write the counter's value. void Increase() { // You can also use lock_guard here. std::unique_lock<std::shared_mutex> lock(mutex_); value_++; } // Only one thread/writer can reset/write the counter's value. void Reset() { std::unique_lock<std::shared_mutex> lock(mutex_); value_ = 0; } private: mutable std::shared_mutex mutex_; std::size_t value_; };
shared_mutex
比通常的 mutex
多了函数 lock_shared() / unlock_shared()
,容许多个(读者)线程同时加锁、解锁,而 shared_lock
则至关于共享版的 lock_guard
。this
对 shared_mutex
使用 lock_guard
或 unique_lock
就达到了写者独占的目的。atom
测试代码:线程
std::mutex g_io_mutex; void Worker(Counter& counter) { for (int i = 0; i < 3; ++i) { counter.Increase(); std::size_t value = counter.Get(); std::lock_guard<std::mutex> lock(g_io_mutex); std::cout << std::this_thread::get_id() << ' ' << value << std::endl; } } int main() { const std::size_t SIZE = 2; Counter counter; std::vector<std::thread> v; v.reserve(SIZE); v.emplace_back(&Worker, std::ref(counter)); v.emplace_back(&Worker, std::ref(counter)); for (std::thread& t : v) { t.join(); } return 0; }
输出(仍然是随机性的):指针
2978 1 4114 2 2978 3 4114 4 4114 6 2978 5
固然,对于计数器来讲,原子类型 std::atomic<>
也许是更好的选择。code
假如一个线程,先做为读者用 shared_lock
加锁,读完后忽然又想变成写者,该怎么办?ip
方法一:先解读者锁,再加写者锁。这种作法的问题是,一解一加之间,其余写者说不定已经介入并修改了数据,那么当前线程做为读者时所持有的状态(好比指针、迭代器)也就再也不有效。rem
方法二:用 upgrade_lock
(仅限 Boost,STL 未提供),能够当作 shared_lock
用,可是必要时能够直接从读者「升级」为写者。
{ // Acquire shared ownership to read. boost::upgrade_lock<boost::shared_mutex> upgrade_lock(shared_mutex_); // Read... // Upgrade to exclusive ownership to write. boost::upgrade_to_unique_lock<boost::shared_mutex> unique_lock(upgrade_lock); // Write... }
惋惜的是,我没能给 upgrade_lock
找到一个颇具实际意义的例子。