锁
或许可以大致分为三个部分 ——
- Mutex 类型
- RAII 锁管理器
- 使用策略和算法
最简单的例子 ——
std::mutex mutex;
void Foo() {
std::lock_guard lock(mutex);
// 临界区资源保护
}Mutex 类型
通常来说,会提供 ——
- mutex.lock();
- mutex.unlock();
- mutex.try_lock();
这三个接口,然后针对不同的 Mutex 类型会有更多其他功能
不过最基础的互斥量还是std::mutex
简单例子 ——
class Counter {
public:
void Increment() {
std::lock_guard lock(mutex_);
++value_;
}
int Get() const {
std::lock_guard lock(mutex_);
return value_;
}
private:
mutable std::mutex mutex_;
int value_{0};
};(此处使用 mutable 是因为在使用的时候会改变内部状态,哪怕在Get方法中使用了const修饰
timed_mutex
支持超时等待的互斥量
额外提供了 ——
try_lock_for():等待一段时间try_lock_until():等待到某个时间点
这两个方法
首先是try_lock_for(),阻塞当前线程等待一段时间,如果这一段时间内锁是可lock的,则返回true,否则返回false
bool TryUseResource() {
if (!mutex.try_lock_for( std::chrono::milliseconds(100) ) ) {
return false;
}
UseResource();
mutex.unlock();
return true;
}当然,这样如果在 UseResource() 的时候抛出异常,则会导致锁无法正常解锁,下面提供一个RAII封装后的写法(虽然这部分放在后面说明逻辑性可能会更强)
bool TryUseResource() {
std::unique_lock lock( mutex, std::defer_lock);
if (!lock.try_lock_for( std::chrono::milliseconds(100))) {
return false;
}
UseResource();
return true;
}try_lock_until() 同理,只是变成了阻塞等待到指定的时间点
shared_mutex
std::shared_mutex 是 Reader/Writer Lock,即读写锁
它有两种不同的获取模式 ——
- 独占锁:Writer 使用
- 共享锁:Reader 使用
lock_shared() 方法的语义为等待获得共享读取权 ——
- 如果当前没有 Writer 持有独占锁: 当前线程获得共享锁
- 如果存在 Writer: 当前线程阻塞等待
对应的是unlock_shared()
不过依旧推荐使用RAII管理器去进行锁的资源管理
RAII 锁管理器
用于管理锁,比如在手动mutex.lock()之后,临界区代码抛出异常则会导致没有unlock()的操作,那很坏了,所以使用RAII包装一下,以便在合适的时刻执行lock和unlock
主要包括:
lock_guardunique_lockscoped_lockshared_lock
lock_guard
最基础的锁管理器,在构造的时候触发.lock(),在析构的时候触发.unlock()
适合最普通的单锁临界区,比如 ——
void Increment() {
std::lock_guard lock(mutex_);
++value_;
}unique_lock
更灵活的独占锁管理器 —— 可以比较自由的决定关联和持有锁的触发时机,同时也支持所有权的转移(完整的移动语义)
延迟加锁
std::unique_lock lock(mutex, std::defer_lock);此时只是关联 Mutex,并没有获得锁
可以在后续手动获得锁(比如lock或者try_lock甚至是try_lock_for,如果关联的锁有定义的话)
同理,也可以在初始化的时候生命非阻塞的尝试获取锁
std::unique_lock lock(mutex, std::try_to_lock);等价于
std::unique_lock lock(mutex, std::defer_lock);
const bool success = lock.try_lock();超时等待
如果关联的锁支持
- try_lock_for();
- try_lock_until();
也可以调用相关接口
比如
std::unique_lock lock(mutex, std::defer_lock);
if (!lock.try_lock_for(
std::chrono::milliseconds(100))) {
return false;
}条件变量
由于条件变量在 wait 的时候必须暂时接管当前线程对 Mutex 的所有权,在等待期间释放它,并在返回前重新获得它,而unique_lock 正是用来表示和管理这种会发生变化的锁所有权,所以才必须传入unique_lock
(挖坑,蹲一个专门写条件变量的 note)
提供一个最基础的例子 ——
std::unique_lock lock(mutex_);
condition_.wait(lock, [this] { return ready_; });scoped_lock
主要用于多锁的情况,它会以死锁规避方式获得所有锁。语义上的理解为 —— 所有锁作为一个整体,在整个作用域内持有
所以它没有unlock方法(如果需要手动进行释放的话,或许使用unique_lock + lock 是一种更好的选择)
值得注意的,此处不能传入两个一样的锁,不然会破坏底层的死锁规避方法
shared_lock
用于管理共享锁,通常搭配 ——
- shared_mutex
- shared_timed_mutex
特点与 unique_lock 类似,但调用的是共享接口