目录
一、生产者消费者问题描述
生产者消费者(producer—customer)问题是一个非常著名的进程同步问题。它描述的是:生产者进程在生产“产品”,消费者进程则消费这些“产品”,在生产者进程和消费者进程之间存在一个缓冲池(生产者将生产的产品放入该缓冲池,消费者则消费该缓冲池中的产品)。所有生产者进程、消费者进程都是相互独立的(即以异步的方式运行),但他们之间同时必须保持同步(即不允许消费者进程到空缓冲区取产品,也不允许生产者进程向满缓冲区存入产品)。
二、解决思路
我们使用队列(先进先出表)Queue来表示上述具有n个缓冲区的缓冲池。每投入(取出)一个产品,缓存池中就相应的插入(删除)一个节点。由于该缓冲池是被组织成队列的形式,因此,队空队满的判断条件分别如下:
q->front == q->rear; //队空
q->front == (q->rear+1)%SIZE; //队满
此外,我们引入一个整型变量num,置其初值为0,当生产者(消费者)进程向缓冲池投入(取走)一个产品时,num对应的加一(减一)。
同时,由于缓冲区是共享的,因此需要对生产者、消费者使用缓冲区进行限制,以此达到同步的效果,即在生产者向缓冲区投入产品时,消费者不得使用缓冲池;消费者向缓冲区取出产品时同理。
三、问题求解
通过以上的分析,我们可以得到一个解决思路如下:
void producer()
{
sem_wait();
lockf();
QueueFull();
Enqueue();
unlockf();
sem_post();
}
void customer()
{
sem_wait();
lockf();
QueueEmpty();
Dequeue();
unlockf();
sem_post();
}
这段伪代码的思路已经十分明确了。
·生产者进程向缓冲区投入产品,则num加一;消费者进程向缓冲区取出产品,则num减一
·为避免生产者进程和消费者进程同时使用资源,我们采用上锁的方式来达到独占资源的目标
·为了避免两个进程之间相互干扰,我们利用信号量机制来实现进程间互斥和同步
但是,仔细思考就会发现,仅仅只是这样是远远不够的。我们还必须保证队满不入、队空不取。因此,在上述结构的基础上,我们加入条件判断机制。
四、源码
由于容器Queue的容量为10,因此我只运行生产者、消费者各十次,且出于简单设计的角度,该程序仅仅展示了单生产者-单消费者的情况。其他的情况,如果后面有空余时间,我会尝试一下的。
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#define TRUE 1
#define FALSE 0
#define SIZE 11
typedef int QueueData; //定义一个整型变量QueueData,与为基本数据类型定义新的别名方法一样
typedef struct _queue //队列结构体
{
int data[SIZE];
int front; // 指向队头的下标
int rear; // 指向队尾的下标
}Queue;
struct data //信号量结构体
{
sem_t count;
Queue q;
};
struct data sem;
pthread_mutex_t mutex; //互斥变量使用特定的数据类型
int num = 0;
int InitQueue (Queue *q) // 队列初始化
{
if (q == NULL)
{
return FALSE;
}
q->front = 0;
q->rear = 0;
return TRUE;
}
int QueueEmpty (Queue *q) //判断空队情况
{
if (q == NULL)
{
return FALSE;
}
return q->front == q->rear;
}
int QueueFull (Queue *q) //判断队满的情况
{
if (q == NULL)
{
return FALSE;
}
return q->front == (q->rear+1)%SIZE;
}
int DeQueue (Queue *q, int x) //出队函数
{
if (q == NULL)
{
return FALSE;
}
if (QueueEmpty(q))
{
return FALSE;
}
q->front = (q->front + 1) % SIZE;
x = q->data[q->front];
return TRUE;
}
int EnQueue (Queue *q, int x) //进队函数
{
if (q == NULL)
{
return FALSE;
}
if (QueueFull(q))
{
return FALSE;
}
q->rear = (q->rear+1) % SIZE;
q->data[q->rear] = x;
return TRUE;
}
void *Producer()
{
int i=0;
while(i<10)
{
i++;
int time = rand() % 10 + 1; //随机使程序睡眠0点几秒
usleep(time * 100000);
sem_wait(&sem.count); //信号量的P操作(使信号量的值减一)
pthread_mutex_lock(&mutex); //互斥锁上锁
if(!QueueFull(&sem.q)) //若队未满
{
num++;
EnQueue (&sem.q, num); //消息进队
printf("生产了一条消息,count=%d\n", num);
}
else printf("Full\n");
pthread_mutex_unlock(&mutex); //互斥锁解锁
sem_post(&sem.count); //信号量的V操作(使信号量的值加一)
}
printf("i(producer)=%d\n",i);
}
void *Customer()
{
int i=0;
while(i<10)
{
i++;
int time = rand() % 10 + 1; //随机使程序睡眠0点几秒
usleep(time * 100000);
sem_wait(&sem.count); //信号量的P操作
pthread_mutex_lock(&mutex); //互斥锁上锁
if(!QueueEmpty(&sem.q)) //若队未空
{
num--;
DeQueue (&sem.q, num); //消息出队
printf("消费了一条消息,count=%d\n",num);
}
else printf("Empty\n");
pthread_mutex_unlock(&mutex); //互斥锁解锁
sem_post(&sem.count); //信号量的V操作
}
printf("i(customer)=%d\n",i);
}
int main()
{
srand((unsigned int)time(NULL));
//信号量地址,信号量在线程间共享,信号量的初始值
sem_init(&sem.count, 0, 10); //信号量初始化(做多容纳10条消息,容纳了10条生产者将不会生产消息)
pthread_mutex_init(&mutex, NULL); //互斥锁初始化
InitQueue(&(sem.q)); //队列初始化
pthread_t producid;
pthread_t consumid;
pthread_create(&producid, NULL, Producer, NULL); //创建生产者线程
pthread_create(&consumid, NULL, Customer, NULL); //创建消费者线程
pthread_join(consumid, NULL); //线程等待,如果没有这一步,主程序会直接结束,导致线程也直接退出。
sem_destroy(&sem.count); //信号量的销毁
pthread_mutex_destroy(&mutex); //互斥锁的销毁
return 0;
}
五、运行结果:
仔细分析可以认为达成实验目的。