C++11中的同步互斥机制详解
文章目录
- 互斥锁:std::mutex
- 什么是 `std::mutex`
- `std::mutex` 的使用
- 存在的问题
- std::lock_guard:自动管理锁的RAII机制
- 什么是 `std::lock_guard`
- 使用 `std::lock_guard`
- std::unique_lock:更灵活的锁管理
- 什么是 `std::unique_lock`
- `std::unique_lock` 的使用
- 条件变量:std::condition_variable
- 什么是 `std::condition_variable`
- 条件变量的使用
- `wait` 的工作原理
- 其他C++11同步机制
在并发编程中,多个线程共享资源时,互斥同步是避免数据竞争和保证数据一致性的关键手段。C++11引入了一系列强大的线程和同步库,包括互斥(mutex
)和条件变量(condition_variable
)
本篇博客将详细介绍 C++11 中的互斥同步机制,重点包括 std::mutex
、std::lock_guard
、std::unique_lock
以及条件变量的使用场景和实现原理。
互斥锁:std::mutex
什么是 std::mutex
std::mutex
是 C++11 提供的最基本的互斥锁,用于保护共享资源,确保同一时刻只有一个线程能够访问该资源。它提供了 lock()
和 unlock()
两个基本方法,分别用于加锁和解锁。
std::mutex
的使用
#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx; // 全局互斥锁
int counter = 0; // 全局共享资源void increment() {mtx.lock(); // 加锁++counter; // 修改共享资源mtx.unlock(); // 解锁
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Final counter: " << counter << std::endl;return 0;
}
在这个例子中,两个线程 t1
和 t2
都在对 counter
进行自增操作。通过 std::mutex
进行加锁和解锁,确保了同一时刻只有一个线程能够访问和修改 counter
,避免了数据竞争。
存在的问题
虽然 std::mutex
提供了基础的互斥功能,但手动调用 lock()
和 unlock()
容易出错。例如,如果程序在 lock()
后发生异常或者忘记调用 unlock()
,就可能导致死锁或资源长时间被占用。因此,C++11 提供了 std::lock_guard
和 std::unique_lock
来简化锁的管理。
std::lock_guard:自动管理锁的RAII机制
什么是 std::lock_guard
std::lock_guard
是 C++11 提供的 RAII(资源获取即初始化)机制的封装类,用于自动管理 mutex
的生命周期。构造时加锁,析构时解锁,确保在函数作用域结束时自动释放锁,避免了手动管理锁的复杂性。
使用 std::lock_guard
void increment() {std::lock_guard<std::mutex> guard(mtx); // 自动加锁++counter; // 修改共享资源
} // 离开作用域时自动解锁
在上面的代码中,当 guard
对象离开作用域时,mutex
会被自动解锁,即使发生异常也不会忘记解锁,从而大大提升了代码的健壮性。
std::unique_lock:更灵活的锁管理
什么是 std::unique_lock
std::unique_lock
是一个比 std::lock_guard
更加灵活的锁管理类。与 std::lock_guard
不同,std::unique_lock
支持:
- 延迟锁定:可以在对象创建时不锁定,而在需要时调用
lock()
。 - 提前解锁:可以手动调用
unlock()
解除锁定。 - 通过
try_lock()
尝试加锁,而不是等待锁定。
std::unique_lock
的使用
void increment() {std::unique_lock<std::mutex> ulock(mtx); // 加锁++counter;ulock.unlock(); // 提前解锁// 其他非共享资源的操作
}
std::unique_lock
提供了更灵活的加锁和解锁控制,非常适合需要手动解锁或多次锁定的场景。尽管灵活性增加,但一般情况下仍推荐使用简单的 std::lock_guard
,除非确实需要这种额外的灵活性。
条件变量:std::condition_variable
什么是 std::condition_variable
std::condition_variable
是一个同步机制,用于让一个线程等待另一个线程满足某个条件。条件变量通常与 std::unique_lock
一起使用,因为它要求有灵活的锁定和解锁操作。
条件变量的使用
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>std::mutex mtx;
std::condition_variable cv;
bool ready = false;void worker() {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, [] { return ready; }); // 等待条件满足std::cout << "Thread is running" << std::endl;
}void signal() {std::this_thread::sleep_for(std::chrono::seconds(1));{std::lock_guard<std::mutex> lock(mtx);ready = true;}cv.notify_one(); // 通知一个等待线程
}int main() {std::thread t1(worker);std::thread t2(signal);t1.join();t2.join();return 0;
}
在这个例子中,worker
线程在等待条件变量 cv
,直到 ready
变为 true
。signal
线程在修改 ready
后,通过 notify_one()
唤醒等待的线程。
wait
的工作原理
wait()
方法会先解锁传入的互斥锁,然后进入阻塞状态。当条件满足时,它会重新加锁并继续执行。条件变量常用于生产者-消费者模型或多线程任务调度。
其他C++11同步机制
除了 std::mutex
及其相关工具,C++11 还提供了其他几种常用的同步机制:
std::recursive_mutex
:允许同一线程多次加锁,而不会导致死锁。std::timed_mutex
:支持超时的mutex
,可以使用try_lock_for
或try_lock_until
进行定时加锁。std::shared_mutex
:允许多个线程共享读锁定,适用于读多写少的场景。