C++ 多线程同步机制详解
1. 互斥量 (Mutex)
什么是互斥量?
互斥量是最基本的同步原语,用于保护共享数据,防止多个线程同时访问。
std::mutex
cpp
#include <mutex>
#include <thread>
#include <iostream>std::mutex mtx;
int shared_data = 0;void increment() {for (int i = 0; i < 100000; ++i) {mtx.lock(); // 获取锁++shared_data; // 临界区mtx.unlock(); // 释放锁}
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Final value: " << shared_data << std::endl; // 总是 200000return 0;
}不同类型的互斥量
cpp
#include <mutex> #include <shared_mutex>// 1. 基本互斥量 std::mutex m1;// 2. 递归互斥量(同一线程可多次锁定) std::recursive_mutex m2;// 3. 定时互斥量(可尝试锁定一段时间) std::timed_mutex m3;// 4. 递归定时互斥量 std::recursive_timed_mutex m4;// 5. 共享互斥量(读写锁) std::shared_mutex m5;
2. 锁管理器 (Lock Guards)
为什么需要锁管理器?
手动管理锁容易忘记解锁,导致死锁。锁管理器基于 RAII 原则,自动管理锁的生命周期。
std::lock_guard
最简单的锁管理器,构造时加锁,析构时自动解锁。
cpp
void safe_increment() {for (int i = 0; i < 100000; ++i) {std::lock_guard<std::mutex> lock(mtx); // 构造时加锁++shared_data; // 临界区} // 析构时自动解锁
}std::unique_lock
更灵活的锁管理器,支持延迟锁定、手动解锁等。
cpp
void flexible_increment() {std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 延迟锁定for (int i = 0; i < 100000; ++i) {lock.lock(); // 手动加锁++shared_data;lock.unlock(); // 手动解锁// 这里可以执行非临界区代码std::this_thread::sleep_for(std::chrono::microseconds(1));lock.lock(); // 重新加锁// 更多临界区操作}
}unique_lock 的特殊功能
cpp
std::timed_mutex tmtx;void timed_operation() {std::unique_lock<std::timed_mutex> lock(tmtx, std::defer_lock);// 尝试锁定最多100毫秒if (lock.try_lock_for(std::chrono::milliseconds(100))) {// 成功获取锁std::cout << "Lock acquired!" << std::endl;} else {// 超时,未能获取锁std::cout << "Failed to acquire lock within timeout" << std::endl;}// 支持移动语义std::unique_lock<std::timed_mutex> another_lock = std::move(lock);
}3. 条件变量 (Condition Variables)
什么是条件变量?
允许线程等待特定条件成立,避免忙等待。
std::condition_variable
cpp
#include <condition_variable>
#include <queue>std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;
bool finished = false;// 生产者
void producer() {for (int i = 0; i < 10; ++i) {std::this_thread::sleep_for(std::chrono::milliseconds(100));std::lock_guard<std::mutex> lock(mtx);data_queue.push(i);std::cout << "Produced: " << i << std::endl;cv.notify_one(); // 通知一个等待的消费者}{std::lock_guard<std::mutex> lock(mtx);finished = true;}cv.notify_all(); // 通知所有消费者
}// 消费者
void consumer(int id) {while (true) {std::unique_lock<std::mutex> lock(mtx);// 等待条件:队列不为空或生产结束cv.wait(lock, [] { return !data_queue.empty() || finished; });// 检查是否应该退出if (finished && data_queue.empty()) {break;}// 处理数据if (!data_queue.empty()) {int data = data_queue.front();data_queue.pop();lock.unlock(); // 尽早释放锁std::cout << "Consumer " << id << " consumed: " << data << std::endl;}}
}条件变量的等待方式
cpp
// 方式1:使用谓词(推荐)
cv.wait(lock, [] { return condition; });// 方式2:无谓词(需要循环检查)
while (!condition) {cv.wait(lock);
}// 带超时的等待
if (cv.wait_for(lock, std::chrono::seconds(1), [] { return condition; })) {// 条件在超时前满足
} else {// 超时
}4. 原子变量 (Atomic Variables)
什么是原子变量?
提供无需锁的线程安全操作,适合简单的计数器、标志位等。
std::atomic
cpp
#include <atomic>
#include <vector>
#include <thread>std::atomic<int> atomic_counter(0);
std::atomic<bool> ready_flag(false);
std::atomic_flag spinlock = ATOMIC_FLAG_INIT;void atomic_worker() {for (int i = 0; i < 10000; ++i) {// 多种原子操作方式// 1. 使用成员函数atomic_counter.fetch_add(1, std::memory_order_relaxed);// 2. 使用运算符重载// atomic_counter++;// 3. 使用 load/store// int old = atomic_counter.load();// atomic_counter.store(old + 1); // 这不是原子的!}
}void spinlock_example() {// 获取自旋锁while (spinlock.test_and_set(std::memory_order_acquire)) {// 忙等待}// 临界区// ...// 释放自旋锁spinlock.clear(std::memory_order_release);
}内存序 (Memory Order)
cpp
std::atomic<int> x(0), y(0);
int r1, r2;void thread1() {x.store(1, std::memory_order_relaxed); // 宽松顺序y.store(1, std::memory_order_release); // 释放操作
}void thread2() {r1 = y.load(std::memory_order_acquire); // 获取操作r2 = x.load(std::memory_order_relaxed);
}// 内存序类型:
// - memory_order_relaxed: 只保证原子性
// - memory_order_consume: 数据依赖顺序
// - memory_order_acquire: 获取操作
// - memory_order_release: 释放操作
// - memory_order_acq_rel: 获取-释放
// - memory_order_seq_cst: 顺序一致性(默认)5. 综合示例:线程安全队列
cpp
#include <queue>
#include <mutex>
#include <condition_variable>
#include <optional>template<typename T>
class ThreadSafeQueue {
private:mutable std::mutex mtx;std::queue<T> queue;std::condition_variable cv;public:ThreadSafeQueue() = default;// 禁止拷贝ThreadSafeQueue(const ThreadSafeQueue&) = delete;ThreadSafeQueue& operator=(const ThreadSafeQueue&) = delete;void push(T value) {std::lock_guard<std::mutex> lock(mtx);queue.push(std::move(value));cv.notify_one();}std::optional<T> pop() {std::unique_lock<std::mutex> lock(mtx);if (queue.empty()) {return std::nullopt;}T value = std::move(queue.front());queue.pop();return value;}T wait_and_pop() {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, [this] { return !queue.empty(); });T value = std::move(queue.front());queue.pop();return value;}bool empty() const {std::lock_guard<std::mutex> lock(mtx);return queue.empty();}size_t size() const {std::lock_guard<std::mutex> lock(mtx);return queue.size();}
};6. 死锁预防
使用 std::lock 同时锁定多个互斥量
cpp
std::mutex mtx1, mtx2;void safe_operation() {// 同时锁定两个互斥量,避免死锁std::lock(mtx1, mtx2);// 使用 lock_guard 管理锁(adopt_lock 表示已锁定)std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);// 临界区操作
}使用 std::scoped_lock (C++17)
cpp
void safer_operation() {std::scoped_lock lock(mtx1, mtx2); // 自动同时锁定多个互斥量// 临界区操作
} // 自动解锁所有互斥量7. 最佳实践总结
优先使用锁管理器而不是手动锁操作
保持临界区最小化 - 尽快释放锁
避免嵌套锁或使用
std::lock预防死锁条件变量总是与互斥量配合使用
简单操作用原子变量,复杂操作用互斥量
注意虚假唤醒 - 条件变量等待总是使用谓词
cpp
// 好:使用谓词避免虚假唤醒
cv.wait(lock, [] { return condition; });// 不好:可能虚假唤醒
while (!condition) {cv.wait(lock);
}这些工具构成了 C++ 多线程编程的基础,正确使用它们可以编写出高效且线程安全的并发程序。
