操作系统实验:消费者-生产者问题
README
本实验报告仅供参考,请勿直接抄袭!
一、实验目的
通过实验,掌握 Windows 和 Linux 环境下互斥锁和信号量的实现方法,加深对临界区问题和进程同步机制的理解,同时熟悉利用 Windows API 和 Pthread API 进行多线程编程的方法。
二、实验内容
- 在 Windows 操作系统上,利用 Win32 API 提供的信号量机制,编写应用 程序实现生产者——消费者问题。
- 在 Linux 操作系统上,利用 Pthread API 提供的信号量机制,编写应用程序实现生产者——消费者问题。
- 两种环境下,生产者和消费者均作为独立线程,并通过 empty、full、mutex 三个信号量实现对缓冲进行插入与删除。
- 通过打印缓冲区中的内容至屏幕,来验证应用程序的正确性。 具体内容可参见“Operating System Concepts (Seventh Edition)” Chapter 6 后的 Project(P236-241)。
三、实验步骤
- 首先写出解决生产者——消费者问题的伪代码,如下图所示:
- 查阅Win32 API提供的信号量机制和Pthread API提供的信号量机制,掌握HANDLE、CreateSemaphore、WaitForSingleObject、ReleaseSemaphore、CreateThread和sem_t、sem_init、sem_wait、sem_post、pthread_create等变量和函数的用法;
- 将第1步中的伪代码实现成可运行的代码:首先声明三个信号量——互斥信号量Mutex、计数信号量Empty和计数信号量Full,再声明缓冲区Buffer(1代表该位置有产品,0代表该位置没有产品),其大小为SizeOfBuffer,其中Empty的初始值为SizeOfBuffer,Full的初始值为0,Mutex的初始值为1;然后分别根据伪代码实现生产者线程produce和消费者线程consume;最后在main()中创建多线程——5个生产者和5个生产者,并使主线程等待一定时间;
四、主要数据结构及其说明
- HANDLE:初始化函数为
HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName)
,其中第一个参数表示安全控制,一般直接传入NULL,第二个参数表示初始资源数量,第三个参数表示最大并发数量,第四个参数表示信号量名称,传入NULL表示匿名信号量,返回值为创建好的HANDLE;
wait操作为DWORD WaitForSingleObject(HANDLE hHandle, DWORDdwMilliseconds)
,其中第一个参数为要等待的HANDLE,第二个参数为Timeout时间,一般设为最大值INFINITE;
signal操作为BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount)
,其中第一个参数为被操作的信号量,第二个参数为增加的值,第三 个参数为指向返回信号量上次值的变量的指针,如果不需要信号量上次的值,那么这个参数可以设置为NULL,该函数如果成功返回TRUE,否则返回FALSE; - sem_t:初始化函数为
int sem_init(sem_t *sem, int pshared, unsigned int value)
,其 中第一个参数为需要初始化的sem_t的地址,第二个参数决定了信号量能否在进程之间共享,第三个参数为该信号量值的大小;
wai操作为int sem_wait(sem_t * sem)
,参数为需要操作的信号量,该函数成功返回0,失败的话信号量的值不变,返回-1;
signal操作为int sem_post(sem_t *sem)
,参数为需要操作的信号量,该函数成功返回0,失败 的话信号量的值不变,返回-1;
五、程序运行时的初值和运行结果
-
Windows操作系统:
-
Linux操作系统:
六、实验体会
实验中产生的错误以及原因分析以及解决过程:在Linux操作系统下使用终端运行程序时,输出文件中总是提示“段错误(核心已转储)”。在CSDN上查阅了相关资料后,首先推断可能是栈空间大小不足,于是在终端中使用ulimit -c unlimited和ulimit -s 819200两条指令扩大源文件大小以及栈空间大小。再次编译运行,发现依然出现相同的问题。排除了栈空间不足这个原因之后,推断可能是访问了不存在的内存地址,即指针越界。于是逐行检查各个涉及到指针的代码的运行状况。最后发现是刚开始的时候为图方便,在没有查看函数参数含义的情况下,直接将创建线程的函数写为了pthread_create(NULL, NULL, produce, NULL),而该函数的前两个参数是不能为NULL的,否则就会出现指针越界。将此处代码修改后,再次编译运行,发现可以正确运行了!
实验的体会及收获:不可以为求省事就不查看函数的参数列表以及每个参数的取值范围,否则就会出现很难排查出来的错误。幸好此次试验的代码体量较小,只有大约100行左右,排查错误较为简单。然而,如果是在大型实验中,像本次这种指针越界的Bug,是非常难排查出来的。所以,在今后的实验中,我不能再犯这种低级错误!
七、源程序
- OnWindows:
#include<iostream>
#include<stdio.h>
#include<windows.h>
using namespace std;
HANDLE Mutex;//互斥信号
HANDLE Full;//计数信号 ,代表目前缓冲区内有几个位置已满
HANDLE Empty;//计数信号 ,代表目前缓冲区内有几个位置是空的
const int SizeOfBuffer = 10;//缓冲区的大小,即有几个位置
int* Buffer;//缓冲区
//显示缓冲区当前的存储情况
void showBuffer()
{
cout << "目前缓冲区的存储情况:";
for (int i = 0; i < SizeOfBuffer; ++i)
{
cout << Buffer[i] << " ";
}
cout << endl;
}
//生产者线程
DWORD WINAPI produce(LPVOID param)
{
do
{
WaitForSingleObject(Empty, INFINITE);//等待缓冲区中有空位
WaitForSingleObject(Mutex, INFINITE);//对缓冲区形成互斥访问
//寻找缓冲区里面的一个空位置
for (int i = 0; i < SizeOfBuffer; ++i)
{
if (Buffer[i] == 0)
{
Buffer[i] = 1;//放置产品
cout<<"ID为"<<GetCurrentThreadId()<<"的生产者在缓冲区的 第"<<i<<"个位置放置一个产品"<<endl;
showBuffer();
break;
}
}
ReleaseSemaphore(Mutex, 1, NULL);//释放互斥信号量Mutex
ReleaseSemaphore(Full, 1, NULL);//使Full加一
} while (true);
return 0;
}
//消费者线程
DWORD WINAPI consume(LPVOID param)
{
do
{
WaitForSingleObject(Full, INFINITE);//等待缓冲区中有产品
WaitForSingleObject(Mutex, INFINITE);//对缓冲区形成互斥访问
//寻找缓冲区里面的一个有产品的位置
for (int i = 0; i < SizeOfBuffer; ++i)
{
if (Buffer[i] == 1)
{
Buffer[i] = 0;//»°◊fl≤˙∆∑
cout << "ID为" << GetCurrentThreadId() << "的消费者在缓冲区的第" << i << "个位置取走了一个产品" << endl;
showBuffer();
break;
}
}
ReleaseSemaphore(Mutex, 1, NULL);//释放互斥信号量Mutex
ReleaseSemaphore(Empty, 1, NULL);//使Empty加一
} while (true);
return 0;
}
int main()
{
Empty = CreateSemaphore(NULL, 10, 10, NULL);//初始化Empty,初始值设为10,最大值为10
Full = CreateSemaphore(NULL, 0, 10, NULL);//初始化Full,初始值设为0,最大值为10
Mutex = CreateSemaphore(NULL, 1, 1, NULL);//初始化Mutex,初始值设为1
//初始化缓冲区
Buffer = new int[SizeOfBuffer];
for (int i = 0; i < SizeOfBuffer; ++i)
{
Buffer[i] = 0;
}
//创建5个生产者线程和5个消费者线程
for (int i = 0; i < 5; ++i)
{
CreateThread(NULL, 0, consume, NULL, 0, NULL);
CreateThread(NULL, 0, produce, NULL, 0, NULL);
}
//让主线程等待一定的时间
Sleep(1000);
}
- OnLinux:
#include<pthread.h>
#include<semaphore.h>
#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
#include<unistd.h>
#include<semaphore.h>
//缓冲区的大小,即有几个位置
#define SizeOfBuffer 5
sem_t Mutex;//互斥信号量
sem_t Full;//计数信号量,代表目前缓冲区内有几个位置已满
sem_t Empty;//计数信号量,代表目前缓冲区内有几个位置为空
int *Buffer;//缓冲区,1代表有产品,0代表没有产品
//显示缓冲区当前的存储情况
void showBuffer()
{
printf("\t缓冲区:");
for(int i = 0; i < SizeOfBuffer; ++i)
{
printf("%d", Buffer[i]);
printf(" ");
}
printf("\n");
}
//生产者线程
void *produce(void *arg)
{
do
{
sem_wait(&Empty);//等待缓冲区中有空位
sem_wait(&Mutex);//对缓冲区形成互斥访问
//寻找缓冲区里面的一个空位置
for(int i = 0; i < SizeOfBuffer; ++i)
{
if(Buffer[i] == 0)
{
Buffer[i] = 1;//放置产品
printf("%s%lu%s%d%s", "ID为", pthread_self(), "的生产者在缓冲区的第", i, "个位置放置了一个产品");
showBuffer();
break;
}
}
sem_post(&Mutex);//释放互斥信号量Mutex
sem_post(&Full);//使Full加1
}while(true);
}
//消费者线程
void *consume(void *arg)
{
do
{
sem_wait(&Full);//等待缓冲区中有空位
sem_wait(&Mutex);//对缓冲区形成互斥访问
//寻找缓冲区里面的一个产品
for(int i = 0; i < SizeOfBuffer; ++i)
{
if(Buffer[i] == 1)
{
Buffer[i] = 0;//取走产品
printf("%s%lu%s%d%s", "ID为", pthread_self(), "的消费者在缓冲区的第", i, "个位置取走了一个产品");
showBuffer();
break;
}
}
sem_post(&Mutex);//释放互斥信号量Mutex
sem_post(&Empty);//使Empty加1
}while(true);
}
int main()
{
//初始化Buffer数组
Buffer = (int*)malloc(SizeOfBuffer*sizeof(int*));
//Buffer = new int[SizeOfBuffer];
for(int i = 0; i < SizeOfBuffer; ++i)
{
Buffer[i] = 0;
}
sem_init(&Mutex, 1, 1);//初始化Mutex为1
sem_init(&Empty, 0, 10);//初始化Empty为10
sem_init(&Full, 0, 0);//初始化Full为0
pthread_t tid[10];
pthread_attr_t attr;
pthread_attr_init(&attr);
//创建5个消费者线程和5个生产者线程
for(int i = 0; i < SizeOfBuffer; ++i)
{
pthread_create(&tid[i], &attr, consume, NULL);
pthread_create(&tid[i + 5], &attr, produce, NULL);
}
sleep(0.999);
return 0;
}