LINUX环境下的生产者-消费者问题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <time.h>
#define N 4 // 消费者或者生产者的数目
#define M 20 // 缓冲数目
int in = 0; // 生产者放置产品的位置
int out = 0; // 消费者取产品的位置
char buff[M]; // 缓冲区
int producter_id = 0; //生产者id
int consumer_id = 0; //消费者id
/* 打印缓冲情况 */
void print()
{
int i;
for(i = 0; i < M; i++)
printf("%c ", buff[i]);
printf("\n");
}
/* 生产者方法 */
void *producter()
{
int id = ++producter_id;
while(1)
{
// 用sleep的数量可以调节生产和消费的速度
sleep(2);
char data;
data=rand()%26+65;
in = in % M;
printf("生产者进程%d在%2d位置产生数据%c: ", id,in,data);
buff[in] = data;
print();
++in;
}
}
/* 消费者方法 */
void *consumer()
{
char data;
int id = ++consumer_id;
while(1)
{
// 用sleep的数量可以调节生产和消费的速度
sleep(1);
out = out % M;
data=buff[out];
printf("消费者进程%d在%2d位置消费数据%c: ",id, out,data);
buff[out] = '*';
print();
++out;
}
}
int main()
{
pthread_t p[N];
pthread_t c[N];
int i;
int ret[N];
for(i=0; i<M; i++)
buff[i]='*'; //'*'表示空,初始化缓冲区
srand((int)time(NULL));
// 创建N个生产者线程
for (i = 0; i < N; i++)
{
ret[i] = pthread_create(&p[i], NULL,(void*)producter, (void *)(&i));
if(ret[i] != 0)
{
printf("producter %d creation failed \n", i);
exit(1);
}
}
//创建N个消费者线程
for(i = 0; i < N; i++)
{
ret[i] = pthread_create(&c[i], NULL, (void*)consumer, NULL);
if(ret[i] != 0)
{
printf("consumer %d creation failed\n", i);
exit(1);
}
}
//销毁线程
for(i = 0; i < N; i++)
{
pthread_join(p[i],NULL);
pthread_join(c[i],NULL);
}
exit(0);
}
操作步骤
打开虚拟机,创建 .c 的C语言文件,将代码编写其中并保存。若代码无误,打开虚拟机终端输入以下指令" gcc C语言文件名 -o 即将创建运行程序名字 -lpthread"
(2) 在代码中加入同步互斥控制功能,使程序能够实现生产者-消费者问题。
实现方法参考教材109页算法,提示:
使用两个同步信号量,sem_t empty_sem; // 同步信号量, 当满了时阻止生产者放产品
sem_t full_sem; // 同步信号量, 当没产品时阻止消费者消费
使用一个互斥信号量,pthread_mutex_t mutex; // 互斥信号量, 一次只有一个线程访问缓冲
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <time.h>
#define N 4 // 消费者或者生产者的数目
#define M 20 // 缓冲数目
int in = 0; // 生产者放置产品的位置
int out = 0; // 消费者取产品的位置
char buff[M]; // 缓冲区
int producter_id = 0; //生产者id
int consumer_id = 0; //消费者id
sem_t empty_sem;
sem_t full_sem;
pthread_mutex_t mutex;
/* 打印缓冲情况 */
void print()
{
int i;
for (i = 0; i < M; i++)
printf("%c ", buff[i]);
printf("\n");
}
/* 生产者方法 */
void *producter(void *arg)
{
int id = ++producter_id;
int producter_ret = sem_init(&empty_sem,0,M);
while (1)
{
// 用sleep的数量可以调节生产和消费的速度
sleep(2);
sem_wait(&empty_sem);
sem_getvalue(&empty_sem,&producter_ret);
pthread_mutex_lock(&mutex);
char data;
data = rand() % 26 + 65;
in = in % M;
printf("生产者进程%d在%2d位置产生数据%c: ", id, in, data);
buff[in] = data;
print();
++in;
pthread_mutex_unlock(&mutex);
sem_post(&full_sem);
}
}
/* 消费者方法 */
void *consumer(void *arg)
{
char data;
int id = ++consumer_id;
int consumer_ret = sem_init(&full_sem,0,0);
while (1)
{
// 用sleep的数量可以调节生产和消费的速度
sleep(1);
sem_wait(&full_sem);
sem_getvalue(&full_sem,&consumer_ret);
pthread_mutex_lock(&mutex);
out = out % M;
data = buff[out];
printf("消费者进程%d在%2d位置消费数据%c: ", id, out, data);
buff[out] = '*';
print();
++out;
pthread_mutex_unlock(&mutex);
sem_post(&empty_sem);
}
}
int main()
{
pthread_t p[N];
pthread_t c[N];
int mutex_lock = pthread_mutex_init(&mutex,NULL);
int i;
int ret[N];
for (i = 0; i < M; i++)
buff[i] = '*'; //'*'表示空,初始化缓冲区
srand((int)time(NULL));
// 创建N个生产者线程
for (i = 0; i < N; i++)
{
ret[i] = pthread_create(&p[i], NULL,producter, (void *)(&i));
if (ret[i] != 0)
{
printf("producter %d creation failed \n", i);
exit(1);
}
}
//创建N个消费者线程
for (i = 0; i < N; i++)
{
ret[i] = pthread_create(&c[i], NULL,consumer, NULL);
if (ret[i] != 0)
{
printf("consumer %d creation failed\n", i);
exit(1);
}
}
//销毁线程
for (i = 0; i < N; i++)
{
pthread_join(p[i], NULL);
pthread_join(c[i], NULL);
}
exit(0);
}
(3) 调整生产者方法、消费者方法中sleep( )函数的参数值,分析运行结果。至少完成如下组合的参数设置:
生产者方法、消费者方法中sleep( )函数参数值均为2;
生产者方法中sleep( )函数参数值均为2、消费者方法中sleep( )函数参数值均为4;
答:运行如下,由于生产速度是消费速度的两倍,缓存区的数据会越来越多,直到堆积满了为止,满了之后每次最多只会有一个空位
生产者方法中sleep( )函数参数值均为4、消费者方法中sleep( )函数参数值均为2;
答:运行如下,由于消费速度是生产速度的两倍,缓存区内的数据总是很少
2. 哲学家进餐问题
(1) 以下代码给出的是哲学家进餐问题的基本框架,只实现了对筷子的互斥控制,会出现死锁情况。理解代码,分析程序运行结果。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <time.h>
#define N 5
pthread_mutex_t chopstick[N];//筷子信号量
//哲学家线程函数
void* philosopher(void* arg){
int *i;
i = (int *)arg;//哲学家序号
for(;;){
//思考
printf("%d 号哲学家在思考......\n",*i);
//sleep(rand()%3);//休眠随机时间,不超过3秒
//等待筷子
pthread_mutex_lock(&chopstick[*i]);
//sleep(rand()%2); //为便于观察死锁现象,可加上这条语句
pthread_mutex_lock(&chopstick[(*i+1)%N]);
//就餐
printf("%d 号哲学家在进餐......\n",*i);
//sleep(rand()%3);//休眠随机时间,不超过3秒
//放回筷子
pthread_mutex_unlock(&chopstick[*i]);
pthread_mutex_unlock(&chopstick[(*i+1)%N]);
}
}
int main(){
pthread_t id[N];
int i;
for(i=0;i<N;i++)
pthread_mutex_init(&chopstick[i],NULL);
for(i=0;i<N;i++)
{ int *p;
p=malloc(sizeof(int));
*p=i;
pthread_create(&id[i],NULL,philosopher,(void*)p);
}
for(i=0;i<N;i++)
pthread_join(id[i],NULL);
}
(2) 教材上给出了三种方式解决哲学家进餐问题的死锁,在以上代码的基础上,至少实现两种解决方法
答:
第一种:每次最多只允许4个哲学家同时拿起左手(或右手)的筷子
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <time.h>
#define N 5
pthread_mutex_t chopstick[N]; //筷子信号量
sem_t allowPhilosopherNum;
//哲学家线程函数
void *philosopher(void *arg)
{
int *i;
i = (int *)arg; //哲学家序号
for (;;)
{
//思考
printf("%d 号哲学家在思考......\n", *i);
sleep(rand()%3);//休眠随机时间,不超过3秒
sem_wait(&allowPhilosopherNum);
//等待筷子
pthread_mutex_lock(&chopstick[*i]);
sleep(rand()%2); //为便于观察死锁现象,可加上这条语句
pthread_mutex_lock(&chopstick[(*i + 1) % N]);
//就餐
printf("%d 号哲学家在进餐......\n", *i);
sleep(rand()%3);//休眠随机时间,不超过3秒
//放回筷子
pthread_mutex_unlock(&chopstick[*i]);
pthread_mutex_unlock(&chopstick[(*i + 1) % N]);
sem_post(&allowPhilosopherNum);
}
}
int main()
{
int ret = sem_init(&allowPhilosopherNum,0,4);
pthread_t id[N];
int i;
for (i = 0; i < N; i++)
pthread_mutex_init(&chopstick[i], NULL);
for (i = 0; i < N; i++)
{
int *p;
p = (int *)malloc(sizeof(int));
*p = i;
pthread_create(&id[i], NULL, philosopher, (void *)p);
}
for (i = 0; i < N; i++)
pthread_join(id[i], NULL);
}
第二种:仅当哲学家的左右两只筷子均可用时,才允许他同时拿起左、右手的两只筷子,否则一支也不拿
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <time.h>
#define N 5
pthread_mutex_t chopstick[N]; //筷子信号量
//哲学家线程函数
void *philosopher(void *arg)
{
int *i;
i = (int *)arg; //哲学家序号
int pickUpLeftChopstick = 0;
int pickUpRightChopstick = 0;
for (;;)
{
pickUpLeftChopstick = 1;
pickUpRightChopstick = 1;
//思考
printf("%d 号哲学家在思考......\n", *i);
sleep(rand() % 3); //休眠随机时间,不超过3秒
//等待筷子
pickUpLeftChopstick = pthread_mutex_trylock(&chopstick[*i]);
//sleep(rand() % 2); //为便于观察死锁现象,可加上这条语句
pickUpRightChopstick = pthread_mutex_trylock(&chopstick[(*i + 1) % N]);
if(pickUpLeftChopstick||pickUpRightChopstick){
if(!pickUpLeftChopstick){
pthread_mutex_unlock(&chopstick[*i]);
}
if(!pickUpRightChopstick){
pthread_mutex_unlock(&chopstick[(*i + 1) % N]);
}
continue;
}
//就餐
printf("%d 号哲学家在进餐......\n", *i);
sleep(rand() % 3); //休眠随机时间,不超过3秒
//放回筷子
pthread_mutex_unlock(&chopstick[*i]);
pthread_mutex_unlock(&chopstick[(*i + 1) % N]);
}
}
int main()
{
pthread_t id[N];
int i;
for (i = 0; i < N; i++)
pthread_mutex_init(&chopstick[i], NULL);
for (i = 0; i < N; i++)
{
int *p;
p = (int *)malloc(sizeof(int));
*p = i;
pthread_create(&id[i], NULL, philosopher, (void *)p);
}
for (i = 0; i < N; i++)
pthread_join(id[i], NULL);
}