一、实验内容:
1.问题描述:一组生产者向一组消费者提供消息,它们共享一个有界缓冲池,生产者向其中投放消息,消费者从中取得消息。假定这些生产者和消费者互相等效,只要缓冲池未满,生产者可将消息送入缓冲池,只要缓冲池未空,消费者可从缓冲池取走一个消息。
2.功能要求:根据进程同步机制,编写一个解决上述问题的程序,可显示缓冲池状态、放数据、取数据等过程。
二、背景知识:
1.进程管理。
2.信号量的有关知识。
三、实验思路:
构建一个阻塞队列来存储生产的数据,队列不满时可以生产,不空时可以消费。先实现构造函数,初始化一个unique_lock供condition_variable使用。在类里面使用unique_lock等需要初始化,并且初始化会加锁的对象。 构造列表初始化,然后函数体里unlock。 申请两个条件变量,分别控制consumer和producer。 然后就是入和出队列, 首先加锁;循环判断一下目前的队列情况,对于各自的特殊情况(队满和队空)进行处理;唤醒一个线程来处理特殊情况;等待处理完毕;处理入和出队列操作;最后释放锁。
四、完整代码:
#include <unistd.h>
#include <cstdlib>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
static const int size = 5; // 缓冲区大小
static const int num = 10; // 产者生产的产品的上限
struct resource {
int buffer[size]; // 产品缓冲区, 配合 read_pos 和 write_pos 模型环形队列.
size_t read_pos; // 消费者读取产品位置.
size_t write_pos; // 生产者写入产品位置.
size_t item_counter;// 当前产品的数量
mutex mtx; // 互斥量,保护产品缓冲区
mutex item_counter_mtx;// 对当前产品数量item_counter这个变量值的互斥更改
condition_variable not_full; // 条件变量, 指示产品缓冲区不为满.
condition_variable not_empty; // 条件变量, 指示产品缓冲区不为空.
} instance; // 产品库全局变量, 生产者和消费者操作该变量.
typedef struct resource resource;
/* 生产者生产一个产品并放入缓冲区的过程 */
void Producer(resource *s, int item)
{
unique_lock<mutex> lock(s->mtx); // 借助互斥信号量 mutex去申请使用缓冲区
while ((( s->write_pos + 1 ) % size)== s->read_pos) { // 缓冲池满,等待
cout << "生产者等待\n"<< endl;
(s->not_full).wait(lock); // 生产者等待"产品库缓冲区不为满"这一条件发生.
}
(s->buffer)[s->write_pos] = item; // 写入产品.
(s->write_pos)++; // 写入位置后移.
if (s->write_pos == size) // 写入位置若是在队列最后则重新设置为初始位置.
s->write_pos = 0;
(s->not_empty).notify_all(); // 通知消费者产品库不为空.
lock.unlock(); // 释放缓冲区对应的mutex
}
/* 消费者消费一个产品的过程 */
int Consumer(resource *s)
{
int data;
unique_lock<mutex> lock(s->mtx);
while (s->write_pos == s->read_pos) {// 缓冲池空,等待
cout << "消费者等待\n"<< endl;
(s->not_empty).wait(lock); // 消费者等待"产品库缓冲区不为空"这一条件发生.
}
data = (s->buffer)[s->read_pos]; // 读取某一产品
(s->read_pos)++; // 读取位置后移
if (s->read_pos >= size) // 读取位置若移到最后,则重新置位.
s->read_pos = 0;
(s->not_full).notify_all(); // 通知消费者产品库不为满.
lock.unlock();
return data; // 返回产品.
}
void ProducerTask() // 生产者任务 —— 不断生产产品,直到规定的产品数量
{
for (int i = 1; i <= num; ++i) {
// sleep(1);
cout << "生产线程生产" << i << "个产品" << endl;
Producer(&instance, i); // 循环生产 num 个产品.
}
cout << "生产线程退出" << endl;
}
void ConsumerTask() // 消费者任务—— 不断消费产品,直到产品无
{
bool ready_to_exit = false;
while (1) {
sleep(1);
unique_lock<mutex> lock(instance.item_counter_mtx);
if (instance.item_counter < num) { // 当前产品数量少于生产产品数量的上限
int item = Consumer(&instance);
++(instance.item_counter);
cout << "消费者线程" << this_thread::get_id()<< "消费第" << item << "个产品" << endl;
}
else ready_to_exit = true;// 如果当前产品数量已达上限,则准备退出。
lock.unlock();
if (ready_to_exit == true)
break;
}
cout << "消费者线程" << this_thread::get_id()<< "退出" << endl;
}
void Initresource(resource *s)
{
s->write_pos = 0; // 初始化产品写入位置.
s->read_pos = 0; // 初始化产品读取位置.
s->item_counter = 0;// 计算当前产品数量
}
int main()
{
Initresource(&instance);
thread producer(ProducerTask); // 创建线程 > 生产者
thread consumer1(ConsumerTask);// 创建线程 > 消费者1号
thread consumer2(ConsumerTask);
thread consumer3(ConsumerTask);
thread consumer4(ConsumerTask);
producer.join(); // 加入线程运行队列
consumer1.join();
consumer2.join();
consumer3.join();
consumer4.join();
}
五、运行结果:
六、结论:
掌握P、V操作及其原理,理解系统如何调度线程,创建线程,初始化线程。程序可能因阻塞问题而导致输出。