今天看到一个代码片段,觉得蛮有意思。
#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。