互斥锁与 RAII
内容介绍
多个线程同时读写同一份数据,如果没有同步,就会产生数据竞争。数据竞争是未定义行为。
std::mutex 用来保护临界区。现代 C++ 不建议手写 lock() / unlock(),而是用 RAII 对象管理锁。
lock_guard
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
int main() {
int counter = 0;
std::mutex m;
std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i) {
threads.emplace_back([&] {
for (int j = 0; j < 10000; ++j) {
std::lock_guard<std::mutex> lock(m);
++counter;
}
});
}
for (auto& t : threads) {
t.join();
}
std::cout << counter << '\n';
}unique_lock
std::unique_lock 比 lock_guard 更灵活,支持延迟加锁、提前解锁、配合条件变量。
std::unique_lock<std::mutex> lock(m);
// do protected work
lock.unlock();
// do unprotected work最佳代码实践
- 优先使用
std::lock_guard,需要条件变量或手动解锁时使用std::unique_lock。 - 锁保护的是数据,不是代码。先明确“哪个 mutex 保护哪个数据”。
- 锁的作用域尽量小。
- 多把锁一起加时,用
std::scoped_lock避免死锁。
常见错误用法
m.lock();
shared_data.push_back(1);
m.unlock();问题:中间如果抛异常,锁不会释放。
std::lock_guard<std::mutex> lock(m);
slow_io();问题:持锁期间做慢 IO,会让其他线程长时间阻塞。
注意事项
mutex不能复制。- 同一个线程重复锁普通
std::mutex会死锁。 recursive_mutex不是常规解法,通常说明职责边界需要重新设计。- 使用锁不能自动保证整体逻辑正确,尤其要小心 check-then-act。
学习路径
- 上一节:cpp线程传参与返回值
- 当前阶段:cpp多线程
- 下一节:cpp条件变量