欢迎光临
我们一直在努力

线程同步与互斥——生产者、消费者模型

1. 引入

生产消费模型,是一种同步关系,是多进程、多线程,同步互斥的一种场景、策略

简称为cp问题,Producer – Consumer

以日常生活的超市为例:

消费者不需要去等待工厂的生产,而可以直接在超市购买;同样地,生产者也不需要等待消费者前来购买,生产出来后,直接存放到超市即可。

超市的存在,带来了明显的优点:

1.1 优势一:缓存

有了超市之后,效率明显地提高,超市就像一个大号的缓存

对于这个缓存,供货商关注的是空间;而消费者关注的是商品数

因为缓存的存在,本质上,调整了供货商和消费者之间 因为速度不一致,而带来的效率问题

1.2 优势二:支持忙闲不均

同时有了超市的存在,支持忙闲不均

比如过年时,供货商要放假,但是消费者要消费, 没关系,有超市的存在,供货商提前生产好商品, 存储在超市,消费者消费时,去超市消费即可

1.3 优势三:解耦

有了超市的存在,生产和消费的行为,进行了一定程度的解耦

供货商在生产商品时,消费者是不关心的; 消费者在使用商品时,供货商也是不关心的

多线程强调并发,并发就要强调解耦,解耦工作做好,并发度才能更好。

2. 实际应用:多线程

实际的应用下,生产者和消费者的角色,都是由线程来承担的。

生产、消费的过程,本质就是,执行流在做通信

我们关注的重点是,如何安全高效的通信

2.1 共享资源的并发问题

内存空间,作为C、P的共享资源,访问时,存在着并发问题

三种关系:

2.1.1 生产者 vs 生产者

生产者 和 生产者 之间,要保证互斥关系

供货商之间,比如康师傅和统一,二者就是竞争关系,理想情况是,只有我一家存在。但是不太可能做到。 但至少要保证,一家供货商,在超市供货时,别的厂家不能进行供货,要等待。

二者是纯竞争关系,就不需要保证顺序了。

2.1.2 消费者 vs 消费者

消费者 和 消费者 之间,也要保证互斥关系

补充:把超市看作100个展柜,那么C1使用1展柜,C2使用2展柜,不就不冲突了吗。 目前就把超市看作整体,不考虑这种情况。

2.1.3 生产者 vs 消费者

生产者 和 消费者 之间,要保证互斥和同步关系

互斥主要是为了保证安全。

摆放(生产)商品和拿取商品的操作,必须是原子的。

不能存在不确定性,比如消费者是否拿到商品取决于,商品是否被摆在了柜台上,可能已经摆上去了,也可能没有,这就会导致不确定性,要避免这种情况。

保证同步。

同时要保证同步,如果供货时频繁去访问超市,消费者就无法访问了,因为要保证二者的互斥,就会导致消费者饥饿问题。所以生产和消费,要有一定顺序性

比如供货商打电话,进行供货,打完这次之后,就不能再打了。 比如要求5天后再打,从而保证消费者的电话能打进来。反之亦是如此

2.2 总结

我们可以使用321原则,概括生产者、消费者模型。

3:3种关系,C和C、P和P、C和P。 2:2种角色,生产者和消费者。注意,每种角色可能有多个 1:1个交易场所,也就是特定结构的内存空间。

优点:

一、支持忙闲不均 二、生产和消费进行解耦

2.2.1 关于耦合

生产和消费,只会通过交易的中间场所,产生一定的耦合,其他情况下,是互不干扰的。

3. 基于BlockingQueue的生产者消费者模型

3.1 代码实现

借助C++的queue,模拟阻塞队列的生产消费模型

队列有上限,也可以为空

单生产者、单消费者的代码如下:

//BlockQueue.hpp

#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>

using namespace std;

template <class T>
class BlockQueue
{
public:
BlockQueue(int max_size = 5) : _max_size(max_size)
{
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_p_cond, nullptr);
pthread_cond_init(&_c_cond, nullptr);
}
~BlockQueue()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_p_cond);
pthread_cond_destroy(&_c_cond);
}

void push(const T &data)
{
pthread_mutex_lock(&_mutex);
if (_q.size() == _max_size) // 可以写成循环?实际上,一定是队列有空位置时,才唤醒生产者
{
pthread_cond_wait(&_p_cond, &_mutex); // 队列满了之后,就不能进行生产了
}

// 1.队列不为满 2.被唤醒并且拿到锁资源
_q.push(data);

// 此时队列一定被生产了一个数据,此时可以唤醒消费者
pthread_cond_signal(&_c_cond);

pthread_mutex_unlock(&_mutex);
}

T pop()
{
pthread_mutex_lock(&_mutex);
if (_q.size() == 0)
{
pthread_cond_wait(&_c_cond, &_mutex); // 队列空了之后,就不能进行消费了
}

// 1.队列不为空 2.被唤醒并且拿到锁资源
T data = _q.front();
_q.pop();

// 此时队列一定被消费了一个数据,此时可以唤醒生产者
pthread_cond_signal(&_p_cond);

pthread_mutex_unlock(&_mutex); // 正确的解锁位置

return data;
}

private:
queue<T> _q; // 所访问的共享资源
pthread_mutex_t _mutex;
pthread_cond_t _p_cond;
pthread_cond_t _c_cond;

int _max_size; // 极值,规定的队列最大容量
};

//main.cpp

#include "BlockQueue.hpp"
#include <unistd.h>

void *Produce(void *args)
{
BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);

int data = 1;
while (1)
{
bq->push(data);

cout << "生产了一个数:" << data << endl;
data++;
// sleep(1);
}
}

void *Consume(void *args)
{
BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);

while (1)
{
sleep(1);
int data = bq->pop();

cout << "消费了一个数:" << data << endl;
}
}

int main()
{
BlockQueue<int> *bq = new BlockQueue<int>;

pthread_t p, c;
pthread_create(&p, nullptr, Produce, bq);
pthread_create(&c, nullptr, Consume, bq);

// 主线程进行等待
pthread_join(p, nullptr);
pthread_join(c, nullptr);

delete bq;

return 0;
}

如图:

3.2 注意事项

3.2.1 提问

链接:豆包的解答

3.3 添加高、低水位线

赞(0)
未经允许不得转载:171主机测评 » 线程同步与互斥——生产者、消费者模型
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址