使用信号量实现生产者消费者模型

场景描述:使用信号量实现生产者和消费者模型,生产者有 5 个,往链表头部添加节点,消费者也有 5 个,删除链表头部的节点。
具体操作函数参考本人本片博客

由于生产者和消费者是两类线程,并且在还没有生成之前是不能进行消费的,在使用信号量处理这类问题的时候可以定义两个信号量,分别用于记录生产者和消费者线程拥有的总资源数。

// 生产者线程 
sem_t psem;
// 消费者线程
sem_t csem;

// 信号量初始化
sem_init(&psem, 0, 5);    // 5个生产者可以同时生产
sem_init(&csem, 0, 0);    // 消费者线程没有资源, 因此不能消费

// 生产者线程
// 在生产之前, 从信号量中取出一个资源
sem_wait(&psem);	
// 生产者商品代码, 有商品了, 放到任务队列
......	 
......
......
// 通知消费者消费,给消费者信号量添加资源,让消费者解除阻塞
sem_post(&csem);
	


// 消费者线程
// 消费者需要等待生产, 默认启动之后应该阻塞
sem_wait(&csem);
// 开始消费
......
......
......
// 消费完成, 通过生产者生产,给生产者信号量添加资源
sem_post(&psem);


通过上面的代码可以知道,初始化信号量的时候没有消费者分配资源,消费者线程启动之后由于没有资源自然就被阻塞了,等生产者生产出产品之后,再给消费者分配资源,这样二者就可以配合着完成生产和消费流程了。

场景一:(总资源数为1)

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
#include<semaphore.h>

//生产者的信号量
sem_t semp;
//消费者的信号量
sem_t semc;

//创建互斥锁
pthread_mutex_t mutex;



//链表节点类型
struct Node
{
    int number;//节点数据值
    struct Node*next;//指向当前节点类型后继节点的地址
};
//创建头结点
struct Node*head=NULL;

//生产者
//此处用的是链表没有上限
void* producer(void* arg)
{
    //一直生产
    while(1)
    {
        sem_wait(&semp);//判断semp里面的资源数为0的时候,所有的生产者都阻塞在这里了

        //创建新节点
        struct Node*newNode=(struct Node*)malloc(sizeof(struct Node));
        //初始化节点
        newNode->number=rand()%1000;//获取随机数,1000以内
        //节点的连接,添加到链表的头部,新节点就是新的头结点
        newNode->next=head;
        //head指针前移
        head=newNode;
        printf("生产者,id:%ld ,number:%d\n",pthread_self(),newNode->number);
        sem_post(&semc);//生产者生产了就通知消费者来消费啦
       
        //生产的慢一点哦
        sleep(rand()%3);//0 1 2
    }

    return NULL;

}
//消费者
//消费者有下限,如果链表为空,就要阻塞消费者线程
void* consumer(void*arg)
{
    while(1)
    {
        sem_wait(&semc);
        //链表的头结点,将其删除
        struct Node*node=head;
        printf("消费者,id:%ld ,number:%d\n",pthread_self(),node->number);
        head=node->next;
        free(node);
 
        sem_post(&semp);//通知生产者生产
        sleep(rand()%3);
    }
    return NULL;
}



int main()
{
    //生产者
    sem_init(&semp,0,1);//资源总数为1
    //消费者->刚开始不能消费,资源数量初始化为0,消费者线程启动就阻塞了
    sem_init(&semc,0,0); 
    //初始化
    pthread_mutex_init(&mutex,NULL);


    //创建两个数组,这两个数组里面来存储子线程创建出来的ID
    pthread_t t1[5],t2[5];
    for(int i=0;i<5;i++){
        pthread_create(&t1[i],NULL,producer,NULL);
    }
    for(int i=0;i<5;i++){
        pthread_create(&t2[i],NULL,consumer,NULL);
    }

    //线程资源的释放
    for(int i=0;i<5;i++){
        //阻塞等待子线程的退出
        pthread_join(t1[i],NULL);
        pthread_join(t2[i],NULL);
    }
    

    pthread_mutex_destroy(&mutex);
    sem_destroy(&semp);
    sem_destroy(&semc);

    return 0;
}

通过测试代码可以得到如下结论:如果生产者和消费者使用的信号量总资源数为 1,那么不会出现生产者线程和消费者线程同时访问共享资源的情况,不管生产者和消费者线程有多少个,它们都是顺序执行的。
在这里插入图片描述

场景二:(总资源数大于1)
如果生产者和消费者线程使用的信号量对应的总资源数为大于 1,这种场景下出现的情况:
1.多个生产者线程同时生产
2.多个消费者同时消费
3.生产者线程和消费者线程同时生产和消费
以上不管哪一种情况都可能会出现多个线程访问共享资源的情况,如果想防止共享资源出现数据混乱,那么就需要使用互斥锁进行线程同步,处理代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
#include<semaphore.h>

//生产者的信号量
sem_t semp;
//消费者的信号量
sem_t semc;

//创建互斥锁
pthread_mutex_t mutex;



//链表节点类型
struct Node
{
    int number;//节点数据值
    struct Node*next;//指向当前节点类型后继节点的地址
};
//创建头结点
struct Node*head=NULL;

//生产者
//此处用的是链表没有上限
void* producer(void* arg)
{
    //一直生产
    while(1)
    {
        //pthread_mutex_lock(&mutex);不能放在sem_wait的上面,会导致所有的生产者阻塞,还有消费者也会阻塞
        sem_wait(&semp);//判断semp里面的资源数为0的时候,所有的生产者都阻塞在这里了
        pthread_mutex_lock(&mutex);
        //创建新节点
        struct Node*newNode=(struct Node*)malloc(sizeof(struct Node));
        //初始化节点
        newNode->number=rand()%1000;//获取随机数,1000以内
        //节点的连接,添加到链表的头部,新节点就是新的头结点
        newNode->next=head;
        //head指针前移
        head=newNode;
        printf("生产者,id:%ld ,number:%d\n",pthread_self(),newNode->number);

        pthread_mutex_unlock(&mutex);

        sem_post(&semc);//生产者生产了就通知消费者来消费啦
        //生产的慢一点哦
        sleep(rand()%3);//0 1 2
    }

    return NULL;

}
//消费者
//消费者有下限,如果链表为空,就要阻塞消费者线程
void* consumer(void*arg)
{
    while(1)
    {
        //pthread_mutex_lock(&mutex);放在上面,如果下面这行代码判断为0然后阻塞,消费者就会阻塞在这里了
        sem_wait(&semc);
         pthread_mutex_lock(&mutex);
        //链表的头结点,将其删除
        struct Node*node=head;
        printf("消费者,id:%ld ,number:%d\n",pthread_self(),node->number);
        head=node->next;
        free(node);

        pthread_mutex_lock(&mutex);
 
        sem_post(&semp);//通知生产者生产
        sleep(rand()%3);
    }
    return NULL;
}



int main()
{
    //生产者,最多有五个生产者可以同时运行,涉及到一个问题同时添加会出现问题,要线程同步了
    //资源总数大于1了
    sem_init(&semp,0,5);
    //消费者->刚开始不能消费,资源数量初始化为0,消费者线程启动就阻塞了
    sem_init(&semc,0,0); 
    //初始化
    pthread_mutex_init(&mutex,NULL);


    //创建两个数组,这两个数组里面来存储子线程创建出来的ID
    pthread_t t1[5],t2[5];
    for(int i=0;i<5;i++){
        pthread_create(&t1[i],NULL,producer,NULL);
    }
    for(int i=0;i<5;i++){
        pthread_create(&t2[i],NULL,consumer,NULL);
    }

    //线程资源的释放
    for(int i=0;i<5;i++){
        //阻塞等待子线程的退出
        pthread_join(t1[i],NULL);
        pthread_join(t2[i],NULL);
    }
    
    
    pthread_mutex_destroy(&mutex);
    sem_destroy(&semp);
    sem_destroy(&semc);

    return 0;
}

在这里插入图片描述
需要注意的是下面这两行的代码顺序都不能交换,否则会造成死锁情况

// 消费者
sem_wait(&csem);
pthread_mutex_lock(&mutex);
// 生产者
sem_wait(&csem);
pthread_mutex_lock(&mutex);

假设某一个消费者线程先运行,调用 pthread_mutex_lock(&mutex); 对互斥锁加锁成功,然后调用 sem_wait(&csem); 由于没有资源,因此被阻塞了。其余的消费者线程由于没有抢到互斥锁,因此被阻塞在互斥锁上。对应生产者线程第一步操作也是调用 pthread_mutex_lock(&mutex);,但是这时候互斥锁已经被消费者线程锁上了,所有生产者都被阻塞,到此为止,多余的线程都被阻塞了,程序产生了死锁。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,下面是一个简单的C语言实现信号版本的生产者消费者模型的代码: ```c #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #define BUFFER_SIZE 5 // 缓冲区大小 #define PRODUCER_NUM 3 // 生产者 #define CONSUMER_NUM 2 // 消费者 int buffer[BUFFER_SIZE]; // 缓冲区 int in = 0, out = 0; // 缓冲区读写指针 sem_t empty, full; // 信号定义 void *producer(void *arg) { int id = *(int*)arg; while (1) { int item = rand() % 1000; // 生产随机数 sem_wait(&empty); // P(empty) buffer[in] = item; // 写入缓冲区 printf("producer %d produce item %d\n", id, item); in = (in + 1) % BUFFER_SIZE; // 修改写指针 sem_post(&full); // V(full) sleep(rand() % 3); // 休眠一段时间 } } void *consumer(void *arg) { int id = *(int*)arg; while (1) { sem_wait(&full); // P(full) int item = buffer[out]; // 读取缓冲区 printf("consumer %d consume item %d\n", id, item); out = (out + 1) % BUFFER_SIZE; // 修改读指针 sem_post(&empty); // V(empty) sleep(rand() % 3); // 休眠一段时间 } } int main() { pthread_t producers[PRODUCER_NUM], consumers[CONSUMER_NUM]; int producer_ids[PRODUCER_NUM], consumer_ids[CONSUMER_NUM]; sem_init(&empty, 0, BUFFER_SIZE); // 初始化 empty 信号 sem_init(&full, 0, 0); // 初始化 full 信号 // 创建生产者线程 for (int i = 0; i < PRODUCER_NUM; i++) { producer_ids[i] = i; pthread_create(&producers[i], NULL, producer, &producer_ids[i]); } // 创建消费者线程 for (int i = 0; i < CONSUMER_NUM; i++) { consumer_ids[i] = i; pthread_create(&consumers[i], NULL, consumer, &consumer_ids[i]); } // 等待线程结束 for (int i = 0; i < PRODUCER_NUM; i++) { pthread_join(producers[i], NULL); } for (int i = 0; i < CONSUMER_NUM; i++) { pthread_join(consumers[i], NULL); } sem_destroy(&empty); // 销毁 empty 信号 sem_destroy(&full); // 销毁 full 信号 return 0; } ``` 在这个例子中,缓冲区是一个大小为 5 的整型数组,生产者线程随机生成一个整数并写入缓冲区,消费者线程读取缓冲区中的整数并打印出来。为了避免生产者消费者同时访问缓冲区的问题,我们使用了两个信号 empty 和 full。其中,empty 表示缓冲区空余的空间,初始值为 BUFFER_SIZE,每次写入缓冲区时需要执行 P(empty) 操作,即将 empty 的值减 1;full 表示缓冲区中已有的数据,初始值为 0,每次读取缓冲区时需要执行 P(full) 操作,即将 full 的值减 1。当 empty 为 0 时,生产者线程会阻塞在 P(empty) 操作上;当 full 为 0 时,消费者线程会阻塞在 P(full) 操作上。代码中的 sleep(rand() % 3) 是为了模拟生产者消费者的不同速度,让程序更加真实。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值