消费者和生产者问题
生产者消费者共享缓冲区,生产者向缓冲区中放数据,消费者从缓冲取中取数据,当缓冲区中被放满时,生产者进程就必须进入挂起状态,直到消费者从缓冲中取走数据时,生产者才能继续向缓冲区中存放数据,同样当缓冲取中没有数据时,消费者进程就必须进入挂起休眠状态,直到生产者向缓冲区中放入数据时,消费者才能被唤醒继续从缓冲区中取走数据。
生产者消费者问题,也称有限缓冲问题,是一个多线程同步问题的经典案例。
分析:
- 1、对于变量empty和full 是去控制缓冲区产品的数量,防止超出缓冲区的大小
假如现在只生产产品,而不去消费产品,empty降为0,full会上涨到100,满足了while内的条件,会陷入死循环,反复打印 “ 缓冲区满”, - 2、还会去加入一个锁,防止一个线程运行的时间片到了,进入其他线程消费或者生产产品,而未记录,产生错误
- 3、如果线程在该时间片复内完成,线程就结束了,如果未完成,系统会将该线程放入就绪队列,等待下制CPU再度调度运行它,而且是接着上次执行的状态接着执行,
- 4、用goto 语句 去防止不断的刷新缓冲区空 或缓冲区满
多消费者和多生产者代码:
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#define N 100
#define true 1
#define producerNum 10
#define consumerNum 5
#define sleepTime 1
//对于线程函数即使不需要参数,也不能写成void 要写成void * a ,在线程创建函数处写NULL
//
typedef int semaphore;
typedef int item;
//
//该缓冲区类似于队列,in 和 out 分别代表位置,入队和出对
item buffer[N] = { 0 };
int in = 0;
int out = 0;
int proCount = 0; //记录产品ID
//empty 和 full 去记录缓冲区内产品数量,
//使用极端例子,假设只生产不消费,empty不断减小,减为0 时 缓冲区满,停止写入数据
semaphore mutex = 1,empty = N, full = 0, proCmutex = 1;
//用锁防止线程运行到中途时间片到
void * producer(void * a)
{
while(true)
{
while(mutex <= 0);
while(empty <= 0)
{
printf("缓冲区已满!\n");
}
empty --;
mutex--;
proCount ++;
buffer[in] = proCount;
in = (in + 1) % N;
printf("生产的产品ID %d,缓冲区位置 %d\n",proCount,in);
mutex ++;
full ++;
sleep(1);
}
}
void *consumer(void * b)
{
while(true)
{
while(mutex <= 0);
while(full <= 0)
{
printf("缓冲区为空\n");
}
full --;
mutex --;
int nextc = buffer[out];
buffer[out] = 0;
out = (out + 1) % N;
printf("\t\t\t\t消费的产品ID %d,缓冲区位置为%d\n",nextc,out);
mutex ++;
empty ++;
sleep(1);
}
}
int main(void)
{
pthread_t pthreadPool[producerNum + consumerNum];
for(int i = 0;i< producerNum;i++)
{
pthread_t temp;
if(pthread_create(&temp,NULL,producer,NULL) == -1)
{
printf("%d : create producer pthread failed\n",__LINE__);
exit(1);
}
pthreadPool[i] = temp;
}
for(int i = 0;i < consumerNum;i++ )
{
pthread_t temp;
if(pthread_create(&temp,NULL,consumer,NULL) == -1)
{
printf("%d : create consumer pthread failed\n",__LINE__);
exit(2);
}
}
void *result;
//使用pthread_join结束线程是因为线程运行会产生碎片垃圾,系统无法自动回收
for(int i = 0;i < producerNum + consumerNum ;i++)
{
if(pthread_join(pthreadPool[i],&result) == -1)
{
printf("failed to recollect\n");
exit(3);
}
}
return 0;
}
单消费者和单生产者代码:
和多消费者多生产者代码基本一致,在创建线程时不同
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<semaphore.h>
//如果线程在该时间片复内完成,线程就结束了,如果未完成,系统会将该线程放入就绪队列,等待下制CPU再度调度运行它,而且是接着上次执行的状态接着执行,
//用goto 语句 去防止不断的刷新缓冲区空 或缓冲区满
typedef int semaphore;
typedef int item;
#define true 1
#define N 3
#define sleepTime 1
item buffer[N] = { 0 };
int in = 0;
int out = 0;
int proCount = 1;
semaphore mutex = 1,empty = N, full = 0,proCmutex = 1;
void * producer(void * a)
{
while(true)
{
while(empty <= 0)
{
printf("缓冲区已满!\n");
goto a;
}
empty --;
while(mutex <= 0);
mutex --;
proCount ++;
buffer[in] = proCount;
in = (in + 1) % N;
printf("生产的产品ID %d,缓冲区位置 %d\n",proCount,in);
mutex++;
full ++;
a:sleep(2);
}
}
void * consumer(void * b)
{
while(true)
{
while(mutex <= 0);
while(full <= 0)
{
printf("缓冲区为空!\n");
goto a;
}
full --;
mutex --;
int nextc = buffer[out];
buffer[out] = 0;
out = (out + 1) % N;
printf("\t\t\t\t消费的产品ID %d,缓冲区位置为%d\n",nextc,out);
mutex++;
empty ++;
a:sleep(sleepTime);
}
}
int main(void)
{
pthread_t proID,conID;
if(pthread_create(&proID,NULL,producer,NULL) == -1)
{
printf("%d : create producer pthread failed \n",__LINE__);
exit(1);
}
if(pthread_create(&conID,NULL,consumer,NULL) == -1)
{
printf("%d : create consumer pthread failed\n",__LINE__);
exit(2);
}
void * result;
if(pthread_join(proID,&result) == -1)
{
printf("%d : recollect producer failed\n",__LINE__);
exit(3);
}
if(pthread_join(conID,&result) == -1)
{
printf("%d : recollect consumer failed\n",__LINE__);
exit(4);
}
return 0;
}
哲学家问题
有五个哲学家绕着圆桌坐,每个哲学家面前有一盘面,两人之间有一支筷子,这样每个哲学家左右各有一支筷子。哲学家有2个状态,思考或者拿起筷子吃饭。如果哲学家拿到一只筷子,不能吃饭,直到拿到2只才能吃饭,并且一次只能拿起身边的一支筷子。一旦拿起便不会放下筷子直到把饭吃完,此时才把这双筷子放回原处。如果,很不幸地,每个哲学家拿起他或她左边的筷子,那么就没有人可以吃到饭了。
哲学家进餐问题是一个多线程运用的经典例子,涉及到线程同步/互斥,临界区访问问题以及死锁问题。
分析:
- 1、原子操作: 指的是有多部操作组成的一个操作。如果该操作不能原子的进行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集
- 2、现代操作系统中,一般都提供了原子操作来实现一些同步操作,所谓原子操作,也就是一个独立而不可分割的操作,在单核环境中,一般意义下原子操作中线程不会被切换,线程切换要么在原子操作之前,要么在原子操作之后。更广泛的意义下原子操作是指一系列必须整体完成的操作步骤,如果任何一步操作没有完成,那么所有完成的步骤,都必须回滚,这样就可以保证要么所有操作步骤都未完成,要么所有操作步骤都被完成
- 3、该问题用了一条汇编指令执行了交换函数,防止交换失败
void xchg(int *x,int *y)
{
__asm__("xchgl %0,%1":"=r" (*x) : "m" (*y));
}
- 4、加入一把全局锁,是为了能让某一个线程得到想要的资源,尽可能满足这一个线程,但是一定要注意解锁位置
- 5、p、v操作
sem_init(&r,0,4)
设置信号量的初始值为4,保证最多有4个线程并行,第5个线程会阻塞
p操作 v操作
s = s - 1 s = s + 1;
if(s >= 0) if(s > 0)
线程继续执行 线程继续执行
if(s < 0) if(s <= 0)
线程阻塞 该线程去唤醒另一个在该信号量上等待的线程,然后继续执行
- 6、各方式原因见:哲学家问题
代码:
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<time.h>
#include<unistd.h>
#include<pthread.h>
#include<semaphore.h>
#define N 5
#define sleepTime 2
//信号量使用的参数
pthread_mutex_t chopsticks [N];
pthread_mutex_t mutex;
sem_t r;
int philosophers[N] = { 0,1,2,3,4 };
//swap指令需要的参数
int islocked[N] = { 0 };
//互斥量使用的参数
pthread_mutex_t chops[N];
//交换函数 ,汇编指令
void xchg(int *x,int *y)
{
__asm__("xchgl %0,%1":"=r" (*x) : "m" (*y));
}
//这个函数使用的解决方法是最多容许四个哲学家拿起左筷子
void philosopher(void * arg)
{
int i = *(int *)arg;
int left = i;
int right = (i + 1) % N;
int leftkey;
int rightkey;
while(1)
{
leftkey = 1;
rightkey = 1;
printf("哲学家 %d正在思考问题\n",i);
sleep(sleepTime);
printf("哲学家 %d饿了\n",i);
sem_wait(&r);
//56-60行 如果想要的筷子被占用,让该线程挂起,陷入死循环
do{
xchg(&leftkey,&islocked[left]);
}while(leftkey);
//筷子属于共享资源,不能同时被拿起,要加锁
pthread_mutex_lock(&chopsticks[left]);
printf("哲学家%d拿起了 %d号筷子,现在只有一支筷子,不能进餐\n",i,left);
do{
xchg(&rightkey,&islocked[right]);
}while(rightkey);
pthread_mutex_unlock(&chopsticks[right]);
printf("哲学家%d拿起了 %d号筷子,现在有两只筷子,开始进餐\n",i,right);
sleep(sleepTime);
islocked[left] = 0;
printf("哲学家%d放下了 %d号筷子\n",i,left);
pthread_mutex_unlock(&chopsticks [left]);
islocked[right] = 0;
printf("哲学家%d放下了 %d号筷子\n",i,right);
pthread_mutex_unlock(&chopsticks[right]);
sem_post(&r);
}
}
void philosopher2(void *arg)
{
int i = *(int *)arg;
int left = i;
int right = ( i + 1 ) % N;
while(1)
{
printf("哲学家%d 正在思考问题\n",i);
sleep(sleepTime);
printf("哲学家%d 饿了\n",i);
if(i % 2 == 0 ) //偶数哲学家,先右后左
{
pthread_mutex_lock(&chopsticks [right]);
printf("哲学家%d拿起了%d号筷子,现在有一支筷子,不能进餐\n",i,right);
pthread_mutex_lock(&chopsticks[left]);
printf("哲学家%d拿起了%d号筷子,现在有两支筷子,开始进餐\n",i,left);
sleep(sleepTime);
pthread_mutex_unlock(&chopsticks [right]);
printf("哲学家%d放下了%d号筷子\n",i,right);
pthread_mutex_unlock(&chopsticks [left]);
printf("哲学家%d放下%d号筷子\n",i,left);
}
else //奇数哲学家,先右后左
{
pthread_mutex_lock(&chopsticks[left]);
printf("哲学家%d拿起了%d号筷子,现在有一支筷子,不能进餐\n",i,left);
pthread_mutex_lock(&chopsticks[right]);
printf("哲学家%d拿起了%d号筷子,现在有两支筷子,开始进餐\n",i,right);
sleep(sleepTime);
pthread_mutex_unlock(&chopsticks[right]);
printf("哲学家%d放下了%d号筷子\n",i,right);
pthread_mutex_unlock(&chopsticks [left]);
printf("哲学家%d放下了%d号筷子\n",i,left);
}
}
}
//加入一把全局锁,是为了能让某一个线程得到想要的资源,尽可能满足这一个线程
//注意解锁位置
void philosopher3(void * arg)
{
int i = *(int *)arg;
int left = i;
int right = (i + 1) % N;
while(1)
{
printf("哲学家%d正在思考问题\n",i);
sleep(sleepTime);
printf("哲学家%d饿了\n",i);
pthread_mutex_lock(&mutex);
pthread_mutex_lock(&chopsticks[left]);
printf("哲学家%d拿起了%d号筷子,现在只有一支筷子,不能进餐\n",i,left);
pthread_mutex_lock(&chopsticks[right]);
printf("哲学家%d拿起了%d号筷子,现在有两支筷子,开始进餐\n",i,right);
pthread_mutex_unlock(&mutex);
sleep(sleepTime);
pthread_mutex_unlock(&chopsticks[left]);
printf("哲学家%d放下了%d号筷子\n",i,left);
pthread_mutex_unlock(&chopsticks[right]);
printf("哲学家%d放下了%d号筷子\n",i,right);
}
}
int main(void)
{
pthread_t PHD[N];
pthread_mutex_init(&mutex,NULL);
sem_init(&r,0,4);
for(int i = 0;i<N;i++)
{
pthread_mutex_init(&chopsticks[i],NULL);
}
for(int i = 0; i< N ;i++)
{
pthread_create(&PHD[i],NULL,(void *)philosopher3,&philosophers[i]);
}
for(int i = 0 ;i < N;i++)
{
pthread_join(PHD[i],NULL);
}
sem_destroy(&r);
for(int i = 0;i < N;i++)
{
pthread_mutex_destroy(&chopsticks[i]);
}
pthread_mutex_destroy(&mutex);
}