程序由C++语言编写,通过定义warehouse结构体实现对仓库的抽象
包含六个函数模块Initwarehouse,Producer,Consumer,ConsumerMotion以及ProducerMotion,以及main函数模块。
其中,Initwarehouse用于初始化仓库warehouse,Producer和Consumer用于描述生产者和消费者,ConsumerMotion和ProducerMotion用于描述生产者和消费者的动作。
仓库结构体包含缓冲区大小,生产者和消费者生产和消费的位置,以及互斥锁和同步信号量。
main函数模块接受程序所必要的输入以及调度安排生产者和消费者的行动顺序,进行相关进程的初始化和回收工作。
2.3详细设计
结构体信息:
结构体warehouse包含buf(int数组)做为仓库容量(缓冲区),通过循环队列的方式访问。以及read_pos,write_pos分别作为消费者读取产品,生产者写入产品的位置。
包含四个互斥锁std::mutex mtx ,对仓库访问进行互斥,stdmtx,对输出进行互斥。pro_mtx生产者互斥生产,con_mtx用于消费者互斥消费。以及用于指示产品缓冲区不满和产品缓冲区不空的两个条件变量std::condition_variable not_full,not_empty。
模块Initwarehouse:
该模块只进行对结构体的初始化工作,初始化产品写入位置,产品读取位置为0,初始化生产者生产的产品序号,消费者消费的产品序号为0,该函数返回空。
模块Producer:
该模块被ProducerMotion调用,用于完成ProducerMotion模块中生产者共同的工作。模块接收仓库指针*wr和产品序号item,以及用于标识当前进程序号的num,然后进行如下操作。
:获得对仓库互斥访问的锁mtx。
:通过设置循环判断(wr->write_pos+ 1) % bufSize) == wr->read_pos这一条件,判断生产者放入的位置是否与消费者消费的位置相同,若相同,则通过not_full阻塞该生产者,表示等待"产品库缓冲区为空"这一条件发生.
:可以写入产品,则写入并write_pos++,表示写入位置后移.并判断写入位置,若是在队列最后则重新设置为初始位置.
:对同步信号量not_empty执行notify_all,唤醒全部消费者进行消费。
模块ProducerMotion:
该模块的动作在while循环中进行,通过设置bool值ready_to_exit判断当前线程是否需要退出循环。
:sleep一秒,并获得生产互斥锁pro_mtx。
:判断生产者生产总数是否大于所需,并判断该生产者生产是否小 于所能生产个数。若大于,设置ready_to_exit为true,准备退出。
:若小于,则调用producr模块进行生产,生产完后输出必要信息并退出,输出消息需要在锁stdmtx下进行。
模块Consumer:
该模块被ConsumerMotion调用,用于完成ConsumerMotion模块中消费者共同的工作。模块接收仓库指针*wr和用于标识当前进程序号的num,最后返回所读取的数值data,具体操作如下。
:获得对仓库互斥访问的锁mtx。
:在while循环中判断write_pos是否等于read_pos,若等于,则通过not_empty信号量阻塞该消费者进程,表示消费者等待"产品库缓冲区不为空"这一条件发生。
:读取缓冲区buf中的信息,并判断是否读到缓冲区末尾,若是则重新置read_pos为0。
:对not_full信号量执行notify_all,通知生产者产品库不为满并返回data给模块ConsumerMotion。
模块ConsumerMotion:
该模块的动作在while循环中进行,通过设置bool值ready_to_exit判断当前线程是否需要退出循环。
:sleep一秒,并获得消费者互斥锁con_mtx。
:判断当前消费者消费的产品con_item_counter是否小于所有,若是,则通过调用consumer模块获得消费产品的序号,并进行必要的信息输出。
:若否,则设置bool值ready_to_exit为false,并退出。
模块main函数:
该模块主要用于进行输入处理,以及创建进程和进程的退出处理,主要步骤如下:
:调用Initwarehouse创建仓库实例。
②:读入生产者个数,消费者个数以及缓冲区容量,分别创建std::thread 类型的prothreads数组和conthreads数组,数组大小分别为m和n,对应生产消费者的个数。
:循环读入生产者可消费的产品个数,存入数组product中。
④:通过调用std::thread(ConsumerMotion,i+1)和std::thread(ProducerMotion,i+1)创建生产者消费者进程。
:调用thread.join等待被创建生产者消费者线程的结束,并回收它们的资源。
2.4调试分析
分析一:C还是C++
C++可以使用std::thread()创建进程,括号内传递一个函数,表示该进程执行的动作,在本次实验中,消费者和生产者的动作序列分别由函数ConsumerMotion()和ProducerMotion()描述。
需注意std::unique_lock对象以独占所有权的方式(unique owership)管理mutex对象的上锁和解锁操作,即在unique_lock对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而unique_lock的生命周期结束之后,它所管理的锁对象会被解锁,
分析二:动态输入消费者和生产者数量
对于动态输入消费者和生产者的数量,比较容易想到用对象数组实现,通过查询可知,可以通过创建线程数组prothreads[]和conthreads[],读入数量m和n后,循环对数组进行赋值,赋值方式为prothreads[i]= std::thread(ProducerMotion,i+1);在thread的构造函数中,第一个参数为线程的入口函数,后面为若干参数。
分析三:简化线程名字
对于一个线程,可以使用std::this_thread::get_id()获得其tid,然而tid的的表示不易于观察试验结果,故考虑简化线程名字,使其可以按序号输出,id为thread类中一个自定义类型,不能强制转换为double或int,且id没有重载%符号。于是考虑创建线程id数组,在输出某线程id时,通过数组对比该线程的序号,然后输出序号即可。查询知在pthread.h头文件中有函数int pthread_equal(pthread_t t1,pthread_tt2)可以比较id类型的数据,可是在实现过程中,该方法实现比较复杂,联想到thread构造函数可以传递自定义参数,即对于语句conthreads[i] =std::thread(ConsumerMotion,i+1);其中i+1表示该线程的顺序,可以实现在该线程执行对应动作时,输出对应序号。
分析四:cout乱序输出
该问题是,在输出中一行中既出现了生产者也出现了消费者,最初考虑到终端对于中文字符的支持不够完善,以为是linux终端多线程输出中文字符乱码的问题,改为英文字符输出验证该想法,却发现依然存在该问题,
考虑到cout是输出流,可能存在这样一种可能,虽然对消费者生产和生产者生产消费动作加锁,但是在某一次的输出时,二者的输出流在cout中没有刷新,导致输出混乱。考虑到在多线程环境中,刷新输出流可能也无法解决问题,依然会被抢占,故考虑加入输出锁std::mutex stdmtx,进行互斥输出,该问题得以解决。
代码如下:
#define BUFFER_SIZE 16 // 缓冲区数量
#define PRO_NO 30 // PRODUCING NO
#define OVER ( - 1) //生产结束标志
#define PSLEEP 10000 // 生产者随机睡眠时间
#define CSLEEP 10000 // 消费者随机睡眠时间
#define PPNO 2 // 生产者数量
#define CPNO 2 // 消费者数量
pthread_mutex_t lock; /* 互斥体lock 用于对缓冲区的互斥操作 */
pthread_cond_t notempty; /* 缓冲区非空的条件变量 */
pthread_cond_t notfull; /* 缓冲区未满的条件变量 */
struct prodcons
{// 缓冲区相关数据结构
int buf[BUFFER_SIZE]; /* 实际数据存放的数组*/
int readpos, writepos; /* 读写指针*/
};
pthread_cond_init(¬empty, NULL);
pthread_cond_init(¬full, NULL);
/* 创建生产者和消费者线程*/
pthread_create(&th_c, NULL, producer, 0);
pthread_create(&th_p, NULL, consumer, 0);
/* 等待两个线程结束*/
pthread_join(th_c, &retval);
pthread_join(th_p, &retval);
//·条件变量的销毁(成功返回0,否则返回其他)
pthread_cond_destroy(¬empty);
pthread_cond_destroy(¬full);
/* 等待缓冲区未满,应该用while判断,因为有可能发送虚假唤醒:期待的条件尚不成立的唤醒。*/
while ((buffer.writepos + 1) % BUFFER_SIZE == buffer.readpos)
//if ((buffer.writepos + 1) % BUFFER_SIZE == buffer.readpos) //会产生虚假唤醒
{
pthread_cond_wait(¬full, &lock);//实现条件等待
}
//激活一个等待该条件的线程,如果该条件变量存在多个等待线程时,按入队顺序激活其中一个。
pthread_cond_signal(¬full);
pthread_cond_signal(¬empty);
void *producer(void *data)
{
int n;
for (n = 0; n <= PRO_NO; n++)
{
pthread_mutex_lock(&lock);
/* 等待缓冲区未满,应该用while判断,因为有可能发送虚假唤醒:期待的条件尚不成立的唤醒。*/
while ((buffer.writepos + 1) % BUFFER_SIZE == buffer.readpos)
//if ((buffer.writepos + 1) % BUFFER_SIZE == buffer.readpos) //会产生虚假唤醒
{
pthread_cond_wait(¬full, &lock);
}
/* 写数据,并移动指针 */
if (n < PRO_NO)
{
buffer.buf[buffer.writepos] = n;
printf("%d --->\n", n);
usleep(PSLEEP);
}
else
{
buffer.buf[buffer.writepos] = OVER;
printf("%d --->\n", OVER);
}
buffer.writepos++;
if (buffer.writepos >= BUFFER_SIZE)
buffer.writepos = 0;
/* 设置缓冲区非空的条件变量*/
pthread_cond_signal(¬empty);
pthread_mutex_unlock(&lock);
}
return NULL;
}
void *consumer(void *data)
{
int d;
while (1)
{
pthread_mutex_lock(&lock);
/* 等待缓冲区非空,应该用while判断,因为有可能发送虚假唤醒:期待的条件尚不成立的唤醒。*/
while(buffer.writepos == buffer.readpos)
//if(buffer.writepos == buffer.readpos) //会产生虚假唤醒
{
pthread_cond_wait(¬empty, &lock);
}
/* 读数据,移动读指针*/
d = buffer.buf[buffer.readpos];
//usleep(CSLEEP);
buffer.readpos++;
if (buffer.readpos >= BUFFER_SIZE)
buffer.readpos = 0;
/* 设置缓冲区未满的条件变量*/
pthread_cond_signal(¬full);
pthread_mutex_unlock(&lock);
printf("--->%d \n", d);
if (d == OVER)
break;
}
return NULL;
}
#include <unistd.h>
#include <cstdlib>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
const int maxn = 200;
int bufSize; // Item buffer size.
static const int ProNum = 3; // How many items we plan to produce.
int product[maxn];
struct resource {
int buf[maxn]; // 产品缓冲区, 配合 read_pos 和 write_pos 模型环形队列.
// int *buf = new int[bufSize];
size_t read_pos; // 消费者读取产品位置.
size_t write_pos; // 生产者写入产品位置.
size_t pro_item_counter;
size_t con_item_counter;
std::mutex mtx; // 互斥量,保护产品缓冲区
std::mutex stdmtx; // 互斥量,保护产品缓冲区
std::mutex pro_mtx;
std::mutex con_mtx;
std::condition_variable not_full; // 条件变量, 指示产品缓冲区不为满.
std::condition_variable not_empty; // 条件变量, 指示产品缓冲区不为空.
} instance; // 产品库全局变量, 生产者和消费者操作该变量.
typedef struct resource resource;
void Producer(resource *ir, int item)
{
// std::cout << "生产者" << num << "被唤醒" << std::endl;
std::unique_lock<std::mutex> lock(ir->mtx);
while (((ir->write_pos + 1) % bufSize) == ir->read_pos) {
std::cout << "生产者" << std::this_thread::get_id() << "被阻塞" << std::endl;
(ir->not_full).wait(lock); // 生产者等待"产品库缓冲区不为满"这一条件发生.
// std::cout << "生产者" << num << "被唤醒" << std::endl;
}
(ir->buf)[ir->write_pos] = item; // 写入产品.
(ir->write_pos)++; // 写入位置后移.
if (ir->write_pos == bufSize) // 写入位置若是在队列最后则重新设置为初始位置.
ir->write_pos = 0;
(ir->not_empty).notify_all(); // 通知消费者产品库不为空.
// std::cout << "消费者" << num << "被唤醒" << std::endl;
}
int Consumer(resource *ir)
{
// std::cout << "消费者" << num << "被唤醒" << std::endl;
int data;
std::unique_lock<std::mutex> lock(ir->mtx);
while (ir->write_pos == ir->read_pos) {
std::cout << "消费者" << std::this_thread::get_id()<< "被阻塞" << std::endl;
(ir->not_empty).wait(lock); // 消费者等待"产品库缓冲区不为空"这一条件发生.
// std::cout << "消费者" << num<< "被唤醒" << std::endl;
}
data = (ir->buf)[ir->read_pos]; // 读取某一产品
(ir->read_pos)++; // 读取位置后移
if (ir->read_pos >= bufSize) // 读取位置若移到最后,则重新置位.
ir->read_pos = 0;
(ir->not_full).notify_all(); // 通知生产者产品库不为满.注意one和all的区别
// std::cout << "生产者" << num << "被唤醒" << std::endl;
return data; // 返回产品.
}
void ProducerTask() // 生产者任务
{
bool ready_to_exit = false;
while (1) {
sleep(1);
std::unique_lock<std::mutex> lock(instance.pro_mtx);
if (instance.pro_item_counter < ProNum) {
++(instance.pro_item_counter);
Producer(&instance, instance.pro_item_counter);
std::unique_lock<std::mutex> stdlock(instance.stdmtx);
std::cout << "生产者" << std::this_thread::get_id()<< "在仓库中放入序号为"
<< instance.pro_item_counter << "的产品" << std::endl;
stdlock.unlock();
}
else
ready_to_exit = true;
lock.unlock();
if (ready_to_exit == true)
break;
}
}
void ConsumerTask() // 消费者任务
{
bool ready_to_exit = false;
while (1) {
sleep(1);
std::unique_lock<std::mutex> lock(instance.con_mtx);
if (instance.con_item_counter < ProNum) {
int item = Consumer(&instance);
++(instance.con_item_counter);
std::unique_lock<std::mutex> stdlock(instance.stdmtx);
//给输入输出加锁
std::cout << "消费者" <<std::this_thread::get_id()
<< "从仓库中消费序号为" << item << "的产品" << std::endl;
stdlock.unlock();
}
else
ready_to_exit = true;
lock.unlock();
if (ready_to_exit == true)
break;
}
}
void Initresource(resource *ir)
{
ir->write_pos = 0; // 初始化产品写入位置.
ir->read_pos = 0; // 初始化产品读取位置.
ir->pro_item_counter = 0;
ir->con_item_counter = 0;
}
int main()
{
Initresource(&instance);
int m, n;
std::cin >> m >> n >> bufSize;
std::thread prothreads[m];
std::thread conthreads[n];
// for (int i = 0; i < m; i++)
// std::cin >> product[i];
// for (int i = 0; i < m; i++)
// prothreads[i] = std::thread(ProducerTask,i+1);
for (int i = 0; i < n; i++)
conthreads[i] = std::thread(ConsumerTask);
for (int i = 0; i < m; i++)
prothreads[i] = std::thread(ProducerTask);
for (int i = 0; i < m; i++)
prothreads[i].join();
for (int i = 0; i < n; i++)
conthreads[i].join();
return 0;
}
#include <unistd.h>
#include <cstdlib>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
const int maxn = 200;
const int all = 5; // all为可生产的最多的数量
int bufSize; // 设置bufsize,定义缓冲区,即仓库容量
int product[maxn]; //定义生产数组表示各个生产者可以生产的数量
struct warehouse {
int buf[maxn]; //仓库容量(缓冲区)为循环队列
size_t read_pos; // 消费者读取产品位置.
size_t write_pos; // 生产者写入产品位置.
size_t pro_item_counter;//生产者生产的产品序号
size_t con_item_counter;//消费者消费的产品序号
std::mutex mtx; // 对仓库访问进行互斥
std::mutex stdmtx; // 对输出进行互斥
std::mutex pro_mtx;//生产者互斥生产
std::mutex con_mtx;//消费者互斥消费
std::condition_variable not_full; // 条件变量指示产品缓冲区不满
std::condition_variable not_empty; // 条件变量指示产品缓冲区不空
} instance; // 仓库全局变量,由生产者和消费者操作
typedef struct warehouse warehouse;
void Initwarehouse(warehouse *wr)
{
wr->write_pos = 0; // 产品写入位置
wr->read_pos = 0; // 产品读取位置
wr->pro_item_counter = 0;
wr->con_item_counter = 0;
}
void Producer(warehouse *wr, int item, int num)
{
std::unique_lock<std::mutex> lock(wr->mtx);
while (((wr->write_pos + 1) % bufSize) == wr->read_pos) {
std::cout << "生产者" << num << "被阻塞" << std::endl;
(wr->not_full).wait(lock); // 生产者等待"产品库缓冲区不为满"这一条件发生.
}
(wr->buf)[wr->write_pos] = item; // 写入产品.
(wr->write_pos)++; // 写入位置后移.
if (wr->write_pos == bufSize) // 写入位置若是在队列最后则重新设置为初始位置.
wr->write_pos = 0;
(wr->not_empty).notify_one(); // 通知消费者产品库不为空.
}
void ProducerMotion(int order) // 生产者动作
{
bool ready_to_exit = false;
while (1) {
sleep(1);
std::unique_lock<std::mutex> lock(instance.pro_mtx);
if (instance.pro_item_counter < all) {
++(instance.pro_item_counter);
Producer(&instance, instance.pro_item_counter, order);//开始生产
std::unique_lock<std::mutex> stdlock(instance.stdmtx);
std::cout << "生产者" << order << "被唤醒" << std::endl;
std::cout << "生产者" << order<< "在仓库中放入序号为"
<< instance.pro_item_counter << "的产品" << std::endl;
stdlock.unlock();
}
else
ready_to_exit = true;
lock.unlock();
if (ready_to_exit == true)
break;
}
}
int Consumer(warehouse *wr, int num)
{
int data;
std::unique_lock<std::mutex> lock(wr->mtx);
while (wr->write_pos == wr->read_pos) {
std::cout << "消费者" << num<< "被阻塞" << std::endl;
(wr->not_empty).wait(lock); // 消费者等待"产品库缓冲区不为空"这一条件发生.
}
data = (wr->buf)[wr->read_pos]; // 读取某一产品
(wr->read_pos)++; // 读取位置后移
if (wr->read_pos >= bufSize) // 读取位置若移到最后,则重新置位.
wr->read_pos = 0;
(wr->not_full).notify_one(); // 通知生产者产品库不为满.注意one和all的区别
return data; // 返回产品.
}
void ConsumerMotion(int order) // 消费者任务
{
bool ready_to_exit = false;
while (1) {
sleep(2);
std::unique_lock<std::mutex> lock(instance.con_mtx);
if (instance.con_item_counter < all) {
int item = Consumer(&instance, order);
++(instance.con_item_counter);
std::unique_lock<std::mutex> stdlock(instance.stdmtx);
//给输入输出加锁
std::cout << "消费者" << order<< "被唤醒" << std::endl;
std::cout << "消费者" << order
<< "从仓库中消费序号为" << item << "的产品" << std::endl;
stdlock.unlock();
}
else
ready_to_exit = true;
lock.unlock();
if (ready_to_exit == true)
break;
}
}
int main()
{
Initwarehouse(&instance);
int m, n;
std::cout << "请输入生产者个数,消费者个数以及缓冲区容量" << std::endl;
std::cin >> m >> n >> bufSize;
std::thread prothreads[m];
std::thread conthreads[n];
std::cout << "请输入各个生产者可以生产的物品个数:" << std::endl;
for (int i = 0; i < m; i++){
std::cin >> product[i];
}
for (int i = 0; i < n; i++)
conthreads[i] = std::thread(ConsumerMotion,i+1);
for (int i = 0; i < m; i++)
prothreads[i] = std::thread(ProducerMotion,i+1);
//C++ 使用join()等待线程运行结束,主进程为main函数
for (int i = 0; i < m; i++)
prothreads[i].join();
for (int i = 0; i < n; i++)
conthreads[i].join();
return 0;
}