场景描述:使用信号量实现生产者和消费者模型,生产者有 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);,但是这时候互斥锁已经被消费者线程锁上了,所有生产者都被阻塞,到此为止,多余的线程都被阻塞了,程序产生了死锁。