1、问题描述
- 现有一个链表作为产品储存区,这个链表为临界资源,生产者和消费者都要访问,
- 5个生产者线程不断生产新的节点插入链表中
- 5个消费者线程不断从链表中读取并删除节点
生产者和消费者需要抢临界资源的互斥锁,并且消费者在消费之前如果发现没有产品,消费者线程就要被阻塞,如果生产者生产出了新产品就要唤醒阻塞的消费者线程
2、思路
- 创建一把互斥锁管理产品储存区,生产者和消费者互斥访问
- 创建一个条件变量,如果消费者消费时发现没有产品,就要被这个条件变量阻塞,生产者生产出了产品就要通过这个条件变量唤醒被阻塞的消费者
3、代码实现
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
//定义条件变量
pthread_cond_t cond;
//定义互斥锁
pthread_mutex_t mutex;
//定义链表结构体
struct Node
{
int number;
struct Node* next;
};
//定义头结点
struct Node* head = NULL;
//当前产品总数
int num = 0;
//生产者线程函数
void* producer(void* arg)
{
while(1)
{
//上锁
pthread_mutex_lock(&mutex);
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); //新节点
newNode->number = rand() % 1000 + 1;
newNode->next = head; //头插法
head = newNode;
num++;
printf("生产者, id:%ld, number:%d, 剩余产品数:%d\n"
, pthread_self(), newNode->number, num);
//解锁
pthread_mutex_unlock(&mutex);
//唤醒所有被条件变量阻塞的消费者线程
pthread_cond_broadcast(&cond);
sleep(rand() % 3); //把CPU时间片交给其他线程,不然执行太快了看不到效果
}
return NULL;
}
//消费者
void* comsumer(void* arv)
{
while(1)
{
//上锁
pthread_mutex_lock(&mutex);
//当头结点为空时说明产品区为空
while(head == NULL)
{
pthread_cond_wait(&cond, &mutex); //阻塞当前线程,等待生产者唤醒
}
struct Node* node = head;
head = head->next; //删头
num--;
printf("消费者, id:%ld, number:%d, 剩余产品数:%d\n"
, pthread_self(), node->number, num);
free(node);
//解锁
pthread_mutex_unlock(&mutex);
sleep(rand() % 3);
}
return NULL;
}
int main()
{
//初始化互斥锁和条件变量
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
//创建线程
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, comsumer, NULL);
}
//回收线程资源,由于生产者线程和消费者线程都是死循环,主线程被阻塞在这里了
for (int i = 0; i < 5; i++)
{
pthread_join(t1[i], NULL);
pthread_join(t2[i], NULL);
}
//销毁互斥锁和条件变量
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
关键问题:
- 消费者线程中pthread_cond_wait()函数要放在while循环中而不是if中,这是因为当所有消费者被阻塞在这里的时候,如果这时候有生产者生产出了一个产品,就会通知所有被阻塞的消费者线程,这些消费者线程抢夺互斥锁,抢到的线程把互斥锁上锁,进入临界区进行消费,消费完后如果依然是消费者抢到了时间片,如果这里使用的是if,那么抢到互斥锁的消费者就会进入临界区,对空的产品区消费,引发错误。因此,需要使用while循环,再判断一次产品区是否为空。
- 生产者每生产一个产品就通知所有消费者线程,如果这时候有被条件变量阻塞的消费者线程,那么就会被唤醒,如果没有也不影响程序的继续执行
本文采用互斥锁和条件变量,还有采用互斥锁和信号量实现生产者消费者模型
(本人学习苏老师课程的笔记和感悟,来源如下:)
作者: 苏丙榅
链接: https://subingwen.cn/linux/thread-sync/#5-1-%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F%E5%87%BD%E6%95%B0
来源: 爱编程的大丙