Chapter 3 锁资源管理和条件变量
1. RAII锁管理类
借助对象实现构造时可自动加锁,析构时自动解锁,并通过{ }控制锁的临界区
1.1 互斥锁管理类lock_guard(C++11)
关键词标记:
adopt_lock标记/adopt_lock_t标记(C++11),用于手动加锁后,想让RAII工具接管解锁的场景,此时不需要再次手动释放锁
1.2 互斥锁管理类unique_lock(C++11)
关键词标记:
-
adopt_lock标记/adopt_lock_t标记(C++11) -
defer_lock延后拥有,只有再次手动上锁后才拥有锁,如果未拥有锁,则出栈区不释放。当unique_lock作为成员变量时需要使用此标记 -
try_to_lock尝试获取锁,不阻塞,获取失败退出栈区不释放
特点:
-
可以调用
unlock()和lock()来临时解锁和上锁 -
可以调用
owns_lock()来判断是否拥有锁,拥有则返回true -
比
lock_guard更加灵活,但是效率、内存占用差一些,同时支持移动构造
1.3 共享锁管理类shared_lock(C++14)
特点:
-
构造时会自动调用共享锁的
lock_shared()即读锁,析构会调用unlock_shared(),因此shared_lock只能管理读锁部分 -
想要管理写锁部分需要使用
unique_lock类,但是unique_lock和shared_lock需要传入同一个共享锁shared_mutex/shared_timed_mutex -
支持移动构造
1.4 多个互斥锁管理类scoped_lock(C++17)
适用场景
static std::mutex mux1;
static std::mutex mux2;
void Scope1
{
mux1.lock();
mux2.lock();
mux1.unlock();
mux2.unlock();
}
void Scope2
{
mux2.lock();
mux1.lock();
mux1.unlock();
mux2.unlock();
}
两个线程可能形成死锁,此时需要借助lock(mux1,mux2)函数(C++11)或者scoped_lock类(C++17)来避免死锁
特点:
-
可同时管理多个锁,它会尝试按顺序获取每个锁。如果在尝试获取下一个锁时失败,它会释放所有已经获取的锁,稍后再重试,以此避免死锁
-
原理类似
lock(mux1,mux2)函数
2. 条件变量
生产者—消费者模型:
- 生产者和消费者共享资源变量
- 生产者生产一个产品,通知消费者消费
- 消费者阻塞等待信号,获取信号后消费产品
定义
std::condition_variable cv;
std::mutex mux;
写线程
{
// 获取锁资源
std::unique_lock lock(mux);
// 写入数据
msgs_.push_back(data);
// 释放锁资源
}
// 通知一个等待信号线程
cv.notify_one();
// 通知所有等待信号线程
// cv.notify_all();
读线程
// 获取锁资源,这里加锁是condition_variable的硬性语义要求
std::unique_lock lock(mux);
// 释放锁资源,并阻塞等待notify_one notify_all通知
cv.wait(lock);
// 处理数据
msgs_.front();
msgs_.pop_front();
成员方法:
-
wait(lck)会原子地执行以下步骤:释放锁资源、阻塞当前线程、被唤醒后重新加锁、返回 -
wait(lck, pred),其中pred可以写入lambda函数,用于返回判断条件,它的源码如下
template <class _Predicate>
void wait(unique_lock<mutex>& _Lck, _Predicate _Pred)
{
while (!_Pred()) {
wait(_Lck);
}
}
真正执行的步骤如下:
- 首先判断
pred是否为真,如果为真,则条件满足,无需调用wait(lck),也就无需等待通知,直接处理 - 如果
pred为假,则调用wait(lck),释放锁资源、阻塞当前线程、被唤醒后重新加锁、返回。然后再次判断pred是否为真。如果为真则处理任务;如果为假则继续重复本过程
关键总结:
- 通知并不是让线程继续执行而是提醒线程可以再检查一次条件
- 如果条件已经满足便绝不等待,无需等待通知
注意:
pred写入的lambda函数中需要添加退出条件,以防止线程一直阻塞,无法退出
cv.wait(lock,[this]{
if(is_exit())
{
return true;
}
return !list_.empty();
});
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 雯欂の修仙笔记!