最平凡日子 最卑微梦想

shared_mutex的代码片段

今天看到一个代码片段,觉得蛮有意思。

#include <iostream>
#include <mutex>  // 对于 std::unique_lock
#include <shared_mutex>
#include <thread>

class ThreadSafeCounter {
 public:
  ThreadSafeCounter() = default;

  unsigned int get() const {
    std::shared_lock<std::shared_mutex> lock(mutex_);
    // 防止多个线程同时写value_,允许多个线程同时读value_
    return value_;
  }

  void increment() {
    std::unique_lock<std::shared_mutex> lock(mutex_);
    // unique_lock和shared_lock都是互斥元,但是unique_lock可以在构造时指定锁的类型,而shared_lock只能是共享锁
    value_++;
  }

  void reset() {
    std::unique_lock<std::shared_mutex> lock(mutex_);
    value_ = 0;
  }

 private:
  mutable std::shared_mutex mutex_;
  unsigned int value_ = 0;
};

int main() {
  ThreadSafeCounter counter;

  auto increment_and_print = [&counter]() {
    for (int i = 0; i < 3; i++) {
      counter.increment();
      std::cout << std::this_thread::get_id() << ' ' << counter.get() << '\n';

      // 注意:写入 std::cout 实际上也要由另一互斥同步。省略它以保持示例简洁。
    }
  };

  std::thread thread1(increment_and_print);
  std::thread thread2(increment_and_print);

  thread1.join();
  thread2.join();
}

可能的输出:

123084176803584 2
123084176803584 3
123084176803584 4
123084185655040 1
123084185655040 5
123084185655040 6

上述输出在单核机器上生成。 thread1 开始时,它首次进入循环并调用increment() , 随后调用 get() 。然而,在它能打印返回值到 std::cout前,调度器将 thread1 置于休眠 并唤醒 thread2,它显然有足够时间一次运行全部三个循环迭代。再回到 thread1 ,它仍在首个循环迭代中,它最终打印其局部的计数器副本的值,即 1 到 std::cout,再运行剩下二个循环。多核机器上,没有线程被置于休眠,且输出更可能为递增顺序。

上述代码很巧妙,也很可能是开发过程中难以调试的bug。