目录
1、等待事件
当一个线程需要等待另一个线程的特定事件时,轮询或者定期检查当然也不失为一个办法。但是这样很不理想。
因为当一个线程等待第二个线程完成任务的过程中,有下面几种一下子就能想到的方案:
- 首先,它可以一直检查共享数据(由互斥元保护)中的标识,并且让第二个线程在任务完成时设置该标识。其次,等待的第一个线程可能拥有别的线程需要的资源。
- 或者你直接让等待线程释放占有的资源,并休眠一定时间(但是要得到恰当的休眠时间是很难的),然后继续请求资源,如果没有拿到能执行的的全部资源,那就继续这个过程。
上面的两种方法都有明显的缺陷,首选方法还是应该使用c++标准库提供的工具等待事件本身。等待由一个线程触发一个事件的最基本机制是条件变量。从概念上说,条件变量与某些事件或其他条件,并且一个或多个线程可以等待该条件被满足。当某个线程已经确定条件得到满足,它就可以通知一个或多个正在条件变量上进行等待的线程,以便于唤醒它们。
2、用条件变量等待条件
std::mutex mut;
std::queue<data_chunk> data_queue;
std::condition_variable data_cond;
void data_preparation_thread()
{
while(more_data_to_prepare)
{
data_chunk const data = prepare_data();
std::lock_guard<std::mutex> lk(mut);
data_queue.push(data);
data_cond.notify_one();
}
}
void data_processing_thread()
{
while(true)
{
std::unique_lock<std::mutex> lk(mut);
data_cond.wait(lk,[]{return !data_queue.empty();}); //①
data_chunk data = data_queue.front();
data_queue.pop();
lk.unlock();
process(data);
if(is_last_chunk(data))
break;
}
}
关于条件变量condition_variable 的详细用法参考博主的另一篇文章std::condition_variable,这里想说的是①处的lambda表达式以及为何使用std::unique_lock而不使用std::lock_guard。
lambda表达式返回队列里是否存在值,若lambda返回值为false,说明data_queue队列里没有值,wait()函数解锁互斥元mut,并阻塞当前线程。等待唤醒(notify_one()/notify_all()),当数据准备线程中对notify_one()的调用通知条件变量时,线程从阻塞状态被唤醒。重新锁定互斥元,并再次检查条件(lambda表达式返回队列里是否存在值),若条件满足(lambda返回值为true),互斥元继续被锁定;如果条件不满足,该线程解锁互斥元,然后继续阻塞。这也是为什么需要std::unique_lock而不是std::lock_guard在处理线程里,因为如果都适用lock_guard,当process线程先执行,互斥元被锁住,wait()没法释放互斥元,则准备数据线程就无法锁定互斥元进行数据准备工作,线程间就可能会出现死锁现象。