在并发编程中,多个线程共享资源时,互斥同步是避免数据竞争和保证数据一致性的关键手段。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
的使用
1 |
|
在这个例子中,两个线程 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
1 | void increment() { |
在上面的代码中,当 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
的使用
1 | void increment() { |
std::unique_lock
提供了更灵活的加锁和解锁控制,非常适合需要手动解锁或多次锁定的场景。尽管灵活性增加,但一般情况下仍推荐使用简单的 std::lock_guard
,除非确实需要这种额外的灵活性。
条件变量:std::condition_variable
什么是 std::condition_variable
std::condition_variable
是一个同步机制,用于让一个线程等待另一个线程满足某个条件。条件变量通常与 std::unique_lock
一起使用,因为它要求有灵活的锁定和解锁操作。
条件变量的使用
1 |
|
在这个例子中,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
:允许多个线程共享读锁定,适用于读多写少的场景。