2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > C++多线程快速入门(三):生产者消费者模型与条件变量使用

C++多线程快速入门(三):生产者消费者模型与条件变量使用

时间:2024-04-05 21:16:35

相关推荐

C++多线程快速入门(三):生产者消费者模型与条件变量使用

互斥锁完成

#include <iostream>#include <deque>#include <thread>#include <mutex>std::deque<int> q;std::mutex mtx;static void produce(int val) {while(val--) {std::unique_lock<std::mutex> guard(mtx);q.push_front(val);mtx.unlock();std::this_thread::sleep_for(std::chrono::seconds(1));}}static void consumer() {int data = INT_MAX;while(data != 0) {std::unique_lock<std::mutex> guard(mtx);if (!q.empty()) {data = q.back();q.pop_back();std::cout << data << std::endl;mtx.unlock();} else {mtx.unlock();}}}void test() {std::thread t1(produce,3);std::thread t2(consumer);t1.join();t2.join();}int main() {test();return 0;}

效果如下:

9876543210Process finished with exit code 1

produce在生产过程中,std::this_thread::sleep_for (std::chrono::seconds(1));表示延时1s,所以生产过程很慢。

consumer存在着一个while循环,只有在接收到表示结束的数据的时候,才会停止,每次循环内部,都是先加锁,判断队列不空,然后就取出一个数,最后解锁。这样其实做了很多无用功,并且CPU占用率很高

可以在consumer内部也加一个小延时,在一次判断后,如果发现队列是空的,那就惩罚一下自己,延时一下,减少CPU的占用率。

static void consumer() {int data = INT_MAX;while(data != 0) {std::unique_lock<std::mutex> guard(mtx);if (!q.empty()) {data = q.back();q.pop_back();std::cout << data << std::endl;mtx.unlock();} else {mtx.unlock();std::this_thread::sleep_for(std::chrono::milliseconds(500));}}}

条件变量改进模型

c++11提供了#include <condition_variable>头文件,std::condition_variable可以和std::mutex结合一起使用,其中有两个重要的接口,notify_one()wait()

wait()可以让线程陷入休眠状态,在消费者生产者模型中,如果生产者发现队列中没有东西,就可以让自己休眠.notify_one()就是唤醒处于wait中的其中一个条件变量.

那什么时刻使用notify_one()比较好呢,当然是在生产者往队列中放数据的时候了,队列中有数据,就可以赶紧叫醒等待中的线程起来干活了。

下面是主要修改代码:

std::condition_variable cond;static void produce(int val) {while(val--) {std::unique_lock<std::mutex> guard(mtx);q.push_front(val);mtx.unlock();cond.notify_one(); // 提醒一个waiting的线程std::this_thread::sleep_for(std::chrono::seconds(1));}}static void consumer() {int data = INT_MAX;while(data != 0) {std::unique_lock<std::mutex> guard(mtx);// 如果队列为空,就一直等直到被notify_one唤醒while(q.empty())cond.wait(guard);data = q.back();q.pop_back();mtx.unlock();std::cout << data << std::endl;}}

此时CPU的占用率也很低,因为在消费者端,队列为空时,将控制权交给了cpu,直到被唤醒。

需要注意的是在判断队列是否为空的时候,使用的是while(q.empty()),而不是if(q.empty())

这是因为wait()从阻塞到返回,不一定就是由于notify_one()函数造成的,还有可能由于系统的不确定原因唤醒(可能和条件变量的实现机制有关),这个的时机和频率都是不确定的,被称作伪唤醒,如果在错误的时候被唤醒了,执行后面的语句就会错误,所以需要再次判断队列是否为空,如果还是为空,就继续wait()阻塞。

在管理互斥锁的时候,使用的是std::unique_lock而不是std::lock_guard,在上一篇笔记C++多线程快速入门(二)共享数据同步以及数据竞争中,谈到过ock_guard没有lock和unlock接口,而unique_lock提供了。这里的话也是由于此点原因。因为在wait()函数之前,使用互斥锁保护了,如果wait的时候什么都没做,岂不是一直持有互斥锁?那生产者也会一直卡住,不能够将数据放入队列中了。所以,wait()函数会先调用互斥锁的unlock()函数,然后再将自己睡眠,在被唤醒后,又会继续持有锁,保护后面的队列操作。

另外除了notify_one()函数,c++还提供了notify_all()函数,可以同时唤醒所有处于wait状态的条件变量。

参考

/qq_43145072/article/details/103732176

往期内容回顾

C++多线程快速入门(二)共享数据同步以及数据竞争

C++多线程快速入门(一):基本&常用操作

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。