0
点赞
收藏
分享

微信扫一扫

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;
}

效果如下:

9
8
7
6
5
4
3
2
1
0
Process 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状态的条件变量。


举报

相关推荐

0 条评论