企业网站设计布局,知识库wordpress插件,重庆市特种作业证报名,建筑公司网站模板免费下载条件变量 —C17 多线程
C标准库提供了条件变量的两种实现#xff1a;std::condition_variable 和std::condition_variable_any。它们都在标准库的头文件condition_variable内声明。两者都需配合互斥#xff0c;方能提供妥当的同步操作。std::condition_variable仅限…条件变量 —C17 多线程
C标准库提供了条件变量的两种实现std::condition_variable 和std::condition_variable_any。它们都在标准库的头文件condition_variable内声明。两者都需配合互斥方能提供妥当的同步操作。std::condition_variable仅限于与std::mutex一起使用然而只要某一类型符合成为互斥的最低标准足以充当互斥std::condition_variable_any即可与之配合使用因此它的后缀是“_any”。由于std::condition_variable_any更加通用它可能产生额外开销涉及其性能、自身的体积或系统资源等因此std::condition_variable应予优先采用除非有必要令程序更灵活
#pragma once
std::mutex mut;
std::queuedata_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_guardstd::mutex lk(mut);data_queue.push(data); ⇽-- - ②}data_cond.notify_one(); ⇽-- - ③}
}
void data_processing_thread() // 由线程甲运行
{while (true){std::unique_lockstd::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;}
}首先我们使用std::queue队列在两个线程之间传递数据①。一旦线程乙准备好数据就使用std::lock_guard锁住互斥以保护队列并压入数据②。然后线程乙调用std::condition_variable实例的成员函数notify_one()通知线程甲③如果它确实正等待着。请注意我们特地使用一个较小的代码块放置压入数据的代码目的是在解锁互斥后通知条件变量。若线程甲立刻觉醒也无须等待互斥解锁从而不会被阻塞。
同时线程甲等待接收处理数据。这次它先对互斥加锁但使用的是std::unique_lock而非std::lock_guard④我们很快会明白缘由。线程甲在std::condition_variable实例上调用wait()传入锁对象和一个lambda函数后者用于表达需要等待成立的条件⑤。
本例中[]{return !data_queue.empty();}是一个简单的lambda函数它检查容器data_queue是否为空。若否则说明已有数据备妥存放在队列中等待处理。 接着wait()在内部调用传入的lambda函数判断条件是否成立若成立lambda函数返回true则wait()返回否则lambda函数返回falsewait()解锁互斥并令线程进入阻塞状态或等待状态。线程乙将数据准备好后即调用notify_one()通知条件变量线程甲随之从休眠中觉醒阻塞解除重新在互斥上获取锁再次查验条件若条件成立则从wait()函数返回而互斥仍被锁住若条件不成立则线程甲解锁互斥并继续等待。我们舍弃std::lock_guard而采用std::unique_lock原因就在这里线程甲在等待期间必须解锁互斥而结束等待之后必须重新加锁但std::lock_guard无法提供这种灵活性。假设线程甲在休眠的时候互斥依然被锁住那么即使线程乙备妥了数据也不能锁住互斥无法将其添加到队列中。结果线程甲所等待的条件永远不能成立它将无止境地等下去。 等待终止的条件判定需要查验队列是否非空⑤为此我们使用了简单的lambda函数。其实也可以向wait()传递普通函数或可调用对象。本例只进行简单判定实际上条件判定的函数有可能更加复杂因而我们需事先另行写出。
那么该判定函数就可以被直接传入无须用lambda表达式包装。在wait()的调用期间条件变量可以多次查验给定的条件次数不受限制在查验时互斥总会被锁住另外当且仅当传入的判定函数返回true时它判定条件成立wait()才会立即返回。如果线程甲重新获得互斥并且查验条件而这一行为却不是直接响应线程乙的通知则称之为伪唤醒spurious wake。
按照C标准的规定这种伪唤醒出现的数量和频率都不确定。故此若判定函数有副作用1则不建议选取它来查验条件。倘若读者真的要这么做就有可能多次产生副作用所以必须准备好应对方法。譬如每次被调用时判定函数就顺带提高所属线程的优先级该提升动作即产生的副作用。结果多次伪唤醒可“意外地”令线程优先级变得非常高。 参考《C并发编程实战第2版》