在上一篇博客中我们了解了什么是生产者与消费者模型?
也掌握了生产者与消费者模型的规则,并且实现了基于单链表的生产者与消费者模型,但是我们实现的基于单链表的生产者与消费者模型还是存在问题,存在死锁的问题,如果想深入理解死锁问题的,可以去看下我总结的死锁的博客。
这篇博客我们主要来实现基于环形队列的生产者与消费者模型。
由于上一篇博客已经讲清楚了关于生产者与消费者的各种问题,所以这篇博客我们就直接来实现。
使用环形队列模拟实现不带锁的单生产者与单消费者模型的思路:
生产者与消费者遵循两个规则:
1.消费者永远都在生产者的后面。
(如果消费者可以超过生产者的话,那么消费者可能会读取到垃圾数据)。
2.生产者永远不能将消费者包围一圈
(如果生产者的优先级很高,消费者的优先级很低,如果生产者可以将消费者包围一圈,那么生产者可能就会覆盖掉原来的旧数据(消费者还没来得及消费的数据))。
由上面的两条规则可知:
1.当生产者将环形队列写满时,这时生产者会被挂起等待。(等待数据被消费者读取走)。
2.当消费者将环形队列中的数据读取完时,这时消费者就会被挂起等待。(等待写入生产者写入的新的数据)。
实现基于环形队列的生产者与消费者模型(不带锁)
#include<stdio.h>
#include<stdlib.h>
#include <semaphore.h>
int blank[64];
sem_t semBlank;//空格子的信号量
sem_t semData;//数据的信号量
//消费者读数据
void *consumer(void *arg)
{
//p
int step = 0;//每次读取数据的下标
while(1)
{
sem_wait(&semData);//等待数据信号量(p操作)
//下面两行是临界区
int data = blank[step];
printf("consume done... %d\n",data);
step++;
sem_post(&semBlank);//释放格子资源(v操作)
step %= 64;//保证下标不越界
// sleep(1);
}
}
void *producter(void *arg)
{
int step = 0;
while(1)
{
sem_wait(&semBlank);//等待格子资源(p操作--)
int data = rand()%1234;
blank[step] = data;
printf("product done... %d\n",data);
step++;
sem_post(&semData);//释放数据资源(v操作++)
step %= 64;
sleep(2);
}
}
int main()
{
sem_init(&semBlank,0,64);
sem_init(&semData,0,0);
pthread_t c,p;
pthread_create(&c,NULL,consumer,NULL);
pthread_create(&p,NULL,producter,NULL);
pthread_join(c,NULL);
pthread_join(p,NULL);
sem_destroy(&semBlank);
sem_destroy(&semData);
return 0;
}
结果如下:
信号量有关操作函数:
1.用sem_init来初始化一个信号量变量:
返回值:
成功返回0,错误返回-1.
参数:
sem:定义的信号量变量的地址;
pshared:如果设置为0,说明这个信号量用于同一个进程的多个线程之间同步的。
如果不为0,则说明这个信号量是在多个进程间共享的。一般应该将其存放在共享内存区。
value:信号量要初始的值(意思也是可用资源的数量)。
2、在使用完信号量之后应该释放与信号量相关的资源。
3.获得资源(P操作)
上面三种情况:
第一种sem_wait通俗点理解,使得信号量的值减一,如果这时的信号变量是0,那么就将该线程/进程挂起等待,也就是阻塞式等待。
第二种sem-trywait是非阻塞等待,如果资源不就绪,那么就返回错误码。
4.释放资源(v操作)
使得信号量的值加1,同时唤醒挂起等待的线程或进程。
返回值:成功返回0,失败返回-1并设置错误码。
描述:
调用该函数可以释放资源(v操作),使得信号量的值加1,同时唤醒挂起等待的线程。