1.生产者与消费者的概念
生产者与消费者(Producer-Consumer)问题,是一个经典的并发编程问题。在这个问题中,涉及到两类进程:生产者和消费者。生产者负责生产数据,并将其放入一个缓冲区中;消费者则从缓冲区中取出数据并消费。
生产者和消费者通常是并发运行的,即它们可能在不同的时间点执行。生产者可能会在生产数据后立即放入缓冲区,而消费者可能会在稍后的时间点从缓冲区中取出数据。因此,需要一种机制来同步生产者和消费者的行为,以确保缓冲区不会溢出(即生产者不会在缓冲区满时继续生产数据)或下溢(即消费者不会在缓冲区空时尝试取出数据)。
生产者与消费者模型是一个经典的并发编程模型,用于解决多线程或进程间的数据共享和同步问题。该模型涉及两类对象:生产者和消费者,以及一个共享的数据缓冲区。
生产者:负责生产数据,并将这些数据放入共享缓冲区中。生产者可以在任何时间点生产数据,并将其添加到缓冲区,供消费者使用。
消费者:负责从共享缓冲区中取出数据,并进行消费(或处理)。消费者可以在任何时间点从缓冲区中取出数据进行处理。
共享缓冲区:是一个中介,用于存储生产者生产的数据,供消费者使用。这个缓冲区通常是有限的,因此生产者和消费者需要一种机制来同步他们的行为,以防止缓冲区溢出(生产者生产的数据太多,消费者来不及消费)或下溢(消费者消费的数据太多,生产者来不及生产)。
生产者-消费者模型中的关键概念和原则包括:
- 互斥:生产者和消费者需要互斥地访问共享缓冲区,以防止数据竞争和不一致的状态。这通常通过使用互斥锁(mutex)或其他同步机制来实现。
- 同步:生产者和消费者之间的同步是确保数据正确流动的关键。生产者不能在缓冲区满时继续生产数据,而消费者也不能在缓冲区空时尝试取出数据。这可以通过使用信号量(semaphores)、条件变量(condition variables)或其他同步机制来实现。
- 死锁和饥饿:在生产者-消费者模型中,需要特别注意避免死锁和饥饿问题。死锁是指多个线程或进程互相等待对方释放资源,导致无法继续执行。饥饿是指某些线程或进程长时间得不到所需的资源,而其他线程或进程却能得到足够的资源。为了避免这些问题,需要合理设计同步机制,并确保资源的公平分配。
初步实现生产者和消费者模型:
使用链表的数据结构,生产者生产数据,即不断地创建新节点,然后添加到链表中。
首先创建链表的数据结构,然后初始化一个头节点。
为了生产者和消费者需要互斥地访问共享缓冲区,那么创建一个互斥量 mutex
// 创建一个互斥量 pthread_mutex_t mutex; //链表节点数据结构 struct Node{ int num; struct Node *next; }; // 头结点 struct Node * head = NULL;
生产者采用头插法向链表中添加数据
//生产者生产数据 void * producer(void * arg) { // 不断的创建新的节点,添加到链表中 while(1) { pthread_mutex_lock(&mutex); struct Node * newNode = (struct Node *)malloc(sizeof(struct Node)); newNode->next = head; head = newNode; newNode->num = rand() % 1000; //随机值 printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self()); pthread_mutex_unlock(&mutex); usleep(100); } return NULL; }
消费者需要读取缓冲区中的数据,然后删除节点,在删除的时候,为了避免溢出,需要先判断链表中是否还有节点可以删除,即if(head != NULL)
//消费者,取内容 void * customer(void * arg) { while(1) { pthread_mutex_lock(&mutex); // 保存头结点的指针 struct Node * tmp = head; // 判断是否有数据 if(head != NULL) { // 有数据 head = head->next; printf