长沙水业网站是哪家公司做的,国外浏览器app,做网站确定什么主题好,wordpress默认参数绪论
并发编程纷繁复杂#xff0c;其中用于线程同步的主要工具——条件变量#xff0c;虽然精悍#xff0c;但是要想正确灵活的运用却并不容易。 对于条件变量的理解有三个难点#xff1a;
为什么wait函数需要将解锁和阻塞、唤醒和上锁这两对操作编程原子的#xff1f;为…绪论
并发编程纷繁复杂其中用于线程同步的主要工具——条件变量虽然精悍但是要想正确灵活的运用却并不容易。 对于条件变量的理解有三个难点
为什么wait函数需要将解锁和阻塞、唤醒和上锁这两对操作编程原子的为什么wait函数需要配合while进行使用通知线程是应该先notify再unlock还是先unlock后notify
希望大家看完下面的介绍能够得到想要的答案。想要了解更多关于C并发编程信息可以移步的我仓库C并发编程
条件变量
C提供了两种条件变量的实现std::condition_variable和std::condition_variable_any。前者只能和std::mutex配合使用后者只需要符合互斥的标准即可。因为std::condition_variable_any更通用所以可能产生额外的开销如果没什么特殊需要尽可能使用std::condition_variable
条件变量是非常重要的线程同步的手段目前我认为是最重要的因此对其的深入理解至关重要。 条件变量总是和互斥一起配合使用互斥用于保护共享数据条件变量用于 通知通知线程判断该共享数据是否满足条件等待线程 通知线程往往先通过互斥保护共享数据对数据进行一定的修改后再发送通知notify_one()、notify_all()。需要注意的是我们应尽可能在临界区内发送通知从而避免可能出现的优先级翻转和条件变量失效问题。虽然临界区外通知可以让等待线程一旦被唤醒就能立即解锁互斥查看是否满足情况但是在Pthread进行wait morphint后基本上两者没有性能上的差距。详细的分析可以参考博客条件变量用例–解锁与signal的顺序问题。 notify_one()理论上只会唤醒一个等待线程适用于共享变量数量发生变化的情况例如通知消息队列中的消息个数增加。notify_all()会唤醒所有等待该条件变量的线程适用于共享变量状态发生变化的情况例如通知所有工作线程开始计算。 等待线程先获得互斥然后将锁和判定条件传递给wait函数等待返回。 wait函数首先会根据判断条件判断是否满足条件返回true 如果满足条件则直接返回互斥依旧上锁 如果不满足条件则阻塞等待并解锁互斥让其他线程得以修改共享数据的状态。直到被notify函数唤醒再次上锁判断条件是否满足。这里的阻塞和解锁、唤醒和上锁都是原子的就是为了避免两个动作分别执行出现的条件竞态。 解锁和阻塞是原子的lock → !pred() → unlock → sleep如果变量的改变以及唤醒事件发生在unlock和sleep中间,那么你不会检测到,也就是错过了这次唤醒。假如下次唤醒依赖于此次唤醒的成功也就是说不会主动唤醒第二次那么将发生死锁。唤醒和上锁是原子的wakeup → lock → !pred 如果条件在wakeup和lock之间从满足变成了不满足不是因为其他等待线程修改而是因为负责唤醒的线程自己再次修改了条件那么此次唤醒将失败。假如后面条件的再次满足依赖于此次条件满足成功也就是说条件不会再主动满足那么将发生死锁。 需要理解的是上面的死锁的出现是有限定条件的例如唤醒之间的依赖、条件满足的依赖虽然大多数情况下没有这么严格的条件但是工具本身需要避免这种危险的情况。 原子操作保证了重要的唤醒和条件满足都能够至少被一个等待线程看到。 可以看到wait函数内部需要解锁互斥所以就不能使用不提供unlock函数的lock_guard而应该使用和互斥有相同接口的unique_lock。 其实C的线程库是对pthread库的封装因此也可以像pthread库一样只传入互斥解锁并等待通知一旦接收到通知后再上锁然后在一个while循环中进行判断。 while (!pred()) {cond_.wait(lk); //调用pthread_cond_wait
}对于传入判定条件的版本其实内部也是这样的一个封装罢了。 之所以说notify_one()理论上只会唤醒一个等待线程是因为存在调用一次notify_one()却唤醒了多个线程的可能性甚至有时候没有调用notify等待线程都被唤醒称这种意外唤醒等待线程的情况为伪唤醒。按照C标准的规定这种伪唤醒出现的数量和频率都不确定因此要求等待线程的判定函数不能有副作用可重用并且需要在唤醒后再次判断条件是否满足如果不满足则需要重新等待。这也是为什么上面的代码使用while进行条件判断而不是if的原因。
消息队列
//
// Created by edward on 22-11-16.
// use condtion_variable to genenrate a thread safe message queue
//#include utils.h
#include mutex
#include queue
#include condition_variable
#include iostream
#include thread
#include stringtemplatetypename T
class MessageQueue {
public:void push(T t) {std::lock_guard lk(mtx_); //互斥保护数据queue_.push(std::move(t));cond_.notify_one(); //临界区内发送通知避免优先级反转和条件变量失效}T pop() {T frnt;std::unique_lock lk(mtx_);cond_.wait(lk, [](){return !queue_.empty();});frnt std::move(queue_.front());queue_.pop();return frnt;}
private:mutable std::mutex mtx_;mutable std::condition_variable cond_;std::queueT queue_;
};using namespace std;templatetypename T
void data_prepare(MessageQueueT messageQueue) {T t;while (cin t) {messageQueue.push(std::move(t));}
}templatetypename T
void data_process(MessageQueueT messageQueue) {T t;int idx 0;while (true) {t messageQueue.pop(); //数据的处理在临界区外edward::print([, idx, ]:, t);}
}int main() {MessageQueuestring messageQueue;edward::print(test begin:);thread preparer(data_preparestring, ref(messageQueue));thread processer(data_processstring, ref(messageQueue));preparer.join();//不用等待processer如果preparer结束则直接推出进程return 0;
}运行结果 其中用到了我自己写的库函数头文件utils如果想要了解更多信息可以移步C 工具函数库