题目简介
生产者和消费者问题是一个经典的多线程同步问题,涉及到一个共享的缓冲区,生产者将数据放入缓冲区,消费者从缓冲区中取出数据。问题的关键是要确保生产者和消费者之间的正确交互,避免生产者在缓冲区满时继续生产,消费者在缓冲区空时继续消费。
解决方案
使用条件变量是一种常见的解决方案,它可以用于线程之间的通信和同步。条件变量允许线程等待某个条件的发生,并在条件满足时被唤醒。在生产者和消费者问题中,可以使用两个条件变量,一个用于生产者等待缓冲区不满的条件,另一个用于消费者等待缓冲区不空的条件。
在这里我使用的是链表版本的存储空间,不需要考虑生产者等待缓冲区满的情况,所以只需要一个条件变量用于消费者等待缓冲区不空即可。
下面是使用条件变量实现生产者和消费者问题的一般步骤:
- 定义共享的缓冲区和相关的变量,如缓冲区大小、缓冲区数据、缓冲区中的数据数量等。
- 定义互斥锁,用于保护对缓冲区的访问,确保同时只有一个线程能够修改缓冲区。
- 定义条件变量,一个用于消费者等待缓冲区不空的条件。
- 生产者线程循环执行以下操作:
- 加锁互斥锁,保证对缓冲区的互斥访问。
- 将数据放入缓冲区。
- 解锁互斥锁。
- 唤醒等待缓冲区不空的条件变量的消费者线程。
- 消费者线程循环执行以下操作:
- 加锁互斥锁,保证对缓冲区的互斥访问。
- 检查缓冲区是否为空,如果为空,则等待缓冲区不空的条件变量。
- 从缓冲区取出数据。
- 解锁互斥锁。
通过使用条件变量,生产者和消费者可以在合适的时机等待和唤醒,以实现正确的同步和互斥操作,避免了忙等待的情况。
编码流程
- 定义了一个结构体
msg
作为链表节点,表示产品。结构体包含一个指向下一个节点的指针next
和一个表示产品编号的整数num
。 - 声明了一个全局变量
head
,表示链表的头指针。 - 静态初始化了一个条件变量
has_product
和一个互斥量lock
。 - 定义了消费者线程函数
consumer
,在一个无限循环中执行消费者的操作。首先,使用互斥锁lock
对临界区进行加锁。然后,使用一个while
循环判断链表头指针head
是否为空,如果为空,说明没有产品可供消费,此时调用pthread_cond_wait
等待条件变量has_product
的触发。在等待期间,消费者线程会释放互斥锁并等待被唤醒。当被唤醒后,再次检查链表头指针是否为空。如果不为空,说明有产品可供消费,将头节点取出并更新头指针。最后,释放互斥锁并打印消费的产品编号,然后释放节点的内存并休眠一段时间。 - 定义了生产者线程函数
producer
,在一个无限循环中执行生产者的操作。首先,动态分配一个新的节点mp
作为产品,并为num
赋予一个随机值。然后,打印生产的产品编号。接下来,使用互斥锁lock
对临界区进行加锁。将新节点插入链表的头部,并更新头指针。最后,释放互斥锁,并调用pthread_cond_signal
唤醒等待在条件变量has_product
上的一个消费者线程。最后,休眠一段时间。 - 在
main
函数中,创建了一个生产者线程和一个消费者线程,并使用pthread_join
等待线程的结束。 - 通过调整休眠时间,可以模拟生产者和消费者的不同速度和数量。
代码展示
//借助条件变量模拟生产者,消费者问题
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
//链表作为共享熟路,需要被互斥量保护
struct msg{
struct msg *next;
int num;
};
struct msg *head;
//静态初始化,一个条件变量和一个互斥量
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void *consumer(void *p){
struct msg *mp;
for(;;){
pthread_mutex_lock(&lock);
while(head == NULL){ //头指针为空,说明没有节点
pthread_cond_wait(&has_product, &lock);
}
mp = head;
head = mp->next; //模拟消费掉一个产片
pthread_mutex_unlock(&lock);
printf("Consume %lu---%d\n", pthread_self(), mp->num);
free(mp);
sleep(rand() % 5);
}
return NULL;
}
void *producer(void *p){
struct msg *mp;
for(;;){
mp = malloc(sizeof(struct msg));
mp->num = rand() % 1000 + 1; //模拟生产一个产品
printf("Produce =============%d\n", mp->num);
pthread_mutex_lock(&lock);
mp->next = head;
head = mp;
pthread_mutex_unlock(&lock);
pthread_cond_signal(&has_product); //将等待在该条件变量的一个线程唤醒
sleep(rand() % 5);
}
return NULL;
}
int main(){
pthread_t pid, cid;
srand(time(NULL));
pthread_create(&pid, NULL, producer, NULL);
pthread_create(&cid, NULL, consumer, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL);
return 0;
}
运行结果
注意事项
需要注意的是,在使用条件变量时,需要先加锁互斥锁,再进行条件判断和等待操作,以确保线程在等待条件变量时不会出现竞态条件。同时,在修改条件变量相关的状态之后,需要唤醒等待该条件的线程,以便它们能够重新检查条件并继续执行。