基于单链表和环形队列的生产消费模型

本文介绍两种生产消费模型的实现方式,一种基于单链表,通过互斥锁和条件变量实现线程间的同步与互斥;另一种基于环形队列,利用信号量控制生产者与消费者的协调工作。
消费者模型

消费者模型由消费者,生产者以及交易场所构成;消费者与生产者是两个线程,对公共存储空间进行访问
其中消费者与生产者的关系有以下几种:

  • 消费者—消费者:互斥
  • 消费者—生产者:互斥、同步
  • 生产者—生产者:互斥
基于单链表的消费者模型

互斥关系的由互斥锁来实现:

int pthread_mutex_lock(pthread_mutex_t *mutex);//上锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁
//以上三个函数成功返回0;

等待则由以下函数实现:

int pthread_cond_timedwait(pthread_cond_t *restrict cond,
              pthread_mutex_t *restrict mutex,
              const struct timespec *restrict abstime);//由time控制的等待
int pthread_cond_wait(pthread_cond_t *restrict cond,
              pthread_mutex_t *restrict mutex);//阻塞式等待

测试用例:
  由一个单向链表实现消费模型,建立两个线程,分别对链表进行头插,头删操作;模拟生产,消费情景:在实现互斥关系时,用互斥锁操作。最终实现:生产者生产一条数据(在链表中头插一个节点,并在屏幕上打印节点中数据),消费者跟着消费一条数据(打印链表节点中的数据)。以下是测试代码:

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<malloc.h>
   
pthread_mutex_t mutex_lock=PTHREAD_MUTEX_INITIALIZER;//init lock
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;//init cond   

ypedef struct SeqList
{
     int data;
     struct SeqList* next;
}SeqList;
  
SeqList* list=NULL;
 
SeqList* Buynode(int d)
{
    SeqList* list=(SeqList*)malloc(sizeof(SeqList));
    list->data=d;
    list->next=NULL;
    return list;
}
void PushFront(SeqList **pplist,int d)
{
	 SeqList* tmp=Buynode(d);
	 tmp->next=*pplist;
	 *pplist=tmp;
}

int PopFront(SeqList** pplist)
{
	if(*pplist==NULL)
	{
		return ;
	}
	SeqList* tmp=(*pplist)->next;
	int ret=(*pplist)->data;
	free(*pplist);
	*pplist=tmp;
	return ret;
 }
  
 void *product(void *arg)
 {
	 while(1)
 {
	 int d=rand()/9527;
	 int i=pthread_mutex_lock(&mutex_lock);//申请锁
	 if(i!=0)
	 {
		 pthread_cond_wait(&cond,&mutex_lock);//阻塞式等待
	 }
	 PushFront(&list,d);
	 printf("product %d\n",d);
	 pthread_mutex_unlock(&mutex_lock);//释放锁
	 sleep(1);
	 }
}

void *consume(void *arg)
{
	while(1)
	{
		sleep(1);
		int i=pthread_mutex_lock(&mutex_lock);//申请锁
		int ret=PopFront(&list);
		if(i!=0)
		{
			pthread_cond_wait(&cond,&mutex_lock);//等待
		}
		printf("consume %d\n",ret);
		pthread_mutex_unlock(&mutex_lock);//释放锁
	}
}
 
int main()
{
	 pthread_t t_a;//定义线程
	 pthread_t t_b;
	 pthread_create(&t_a,NULL,product,(void*)NULL);//确定线程入口点
	 pthread_create(&t_b,NULL,consume,(void*)NULL);
	 pthread_join(t_b,NULL);//等待线程退出
	 pthread_mutex_destroy(&mutex_lock);//销毁锁
	 pthread_cond_destroy(&cond);//销毁条件变量
	 return 0;
}

测试结果
这里写图片描述


基于环形队列的生产消费模型

在测试用例中先规定格子数为4,生产者先生产(打印生产好的数据),消费者每隔一秒消费一次(读取数据并打印)
这里写图片描述
实现条件

  1. 生产者不能超过消费者一圈
  2. 消费者永远跟在生产者后面

测试代码

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
#define SIZE 4
 
int arr[SIZE]={0};
int ret = 0;
sem_t datasem;
sem_t blanksem;

void *product(void *arg)
{
	int i=0;
	while(1)
	{
		sem_wait(&blanksem);//有格子资源时,生产者开始生产
		arr[ret]=i;
		printf("product done! %d\n", arr[ret]);
		sem_post(&datasem);//给消费者发出数据资源信号
		i++;
		ret++;
		ret %= SIZE;
	 }
}

void *consume(void *arg)
{
	while(1)
	{
		sem_wait(&datasem);//申请,当有数据资源时,消费者开始消费
		printf("consume done! %d\n", arr[ret]);
		sem_post(&blanksem);//释放,并给生产者发出格子空出信号
		sleep(1);
	}
}

int main()
{
	pthread_t c;//定义线程
	pthread_t p;
	sem_init(&datasem, 0, 0);//初始化信号量
	sem_init(&blanksem,0, SIZE);
	pthread_create(&p, NULL, product, NULL);//确定线程入口
	pthread_create(&c, NULL, consume, NULL);
	pthread_join(c, NULL);//等待线程退出
	pthread_join(p, NULL);
	sem_destroy(&datasem);//销毁信号量
	sem_destroy(&blanksem);
	return 0;
}

测试结果
这里写图片描述

以上测试结果可以看出,在生产了四个数据之后,消费者开始消费,直到消费完空出格子,生产者开始生产,等着每隔一秒消费者消费后,生产者接着生产。

在编译时注意加上-lpthread参数,以调用静态链接库。因为pthread并非Linux系统的默认库

若有不正之处,恳请留言指正

<think>首先,用户的问题是:用C语言实现基于单链表的多生产消费消息队列。我需要构建一个清晰的回答结构,确保真实可靠。 参考系统指令: - 使用中文回答。 - 行内数学表达式用$...$。 - 独立公式用$$...$$。 - 生成相关问题。 - 在引用的段落末尾自然地添加引用标识。 用户提供的引用: - [^1]: 描述了一个链表操作,如eatspit函数,用于管理节点。 - [^2]: 关于C语言实现队列的参考。 - [^3]: 栈队列的实现细节。 - [^4]: 队列的基本定义。 这些引用不是用户直接需求,但用户希望实现一个基于单链表的消息队列,适用于生产者消费者场景。 关键点: - 多生产者:多个线程可以同时添加消息到队列。 - 单消费者:一个线程从队列中取出消息。 - 基于单链表:使用单链表作为数据结构。 - 需要线程安全,因为是多生产者。 实现步骤: 1. **定义数据结构**: - 队列节点:包含数据指向下一个节点的指针。 - 队列结构:包含头指针、尾指针,以及可能的同步机制(如互斥锁)。 2. **同步机制**: - 由于是多生产者,需要互斥锁来保护队列操作。 - 消费者也需要同步,但因为是单消费者,可能不需要额外的锁,但为了安全,最好使用锁。 3. **函数实现**: - 初始化队列。 - 生产者入队(enqueue):添加消息到队尾。 - 消费者出队(dequeue):从队头取出消息。 - 销毁队列。 4. **线程安全**: - 使用pthread库的互斥锁(mutex)来保护共享资源(队列)。 5. **代码结构**: - 定义在头文件中。 - 实现在源文件中。 基于引用[^1],提到“eat”“spit”函数,类似于消费者生产者操作。我会参考这个命名风格。 回答结构: 1. **介绍**:简述消息队列需求。 2. **数据结构定义**:使用C代码块。 3. **函数实现**:包括初始化、入队、出队、销毁。 4. **同步机制说明**。 5. **完整代码示例**。 6. **生成相关问题**。 确保LaTeX语法正确:如果有数学表达式,但这里可能不需要,除非有性能分析等。 生成相关问题: - 类似示例,如: 1. 如何优化多生产者消费者队列的性能? 2. 如何处理队列满或空的情况? 3. 这个队列在什么场景下应用? 引用标识:在回答中自然地添加,如引用[^1]的地方。 用户上一次问题:系统指令中用户说“参考用户上一次的问题”,但在这个对话中,这是第一个消息,所以可能没有。用户说“请尽可能多的参考用户上一次的问题”,但在这个上下文,我假设这是初始问题。 构建回答: - **标题**:基于单链表的多生产者消费者消息队列实现 - **内容**: - 介绍:解释消息队列,多生产者消费者场景。 - 数据结构:定义节点队列结构。 - 初始化函数。 - 入队函数(生产者)。 - 出队函数(消费者)。 - 销毁函数。 - 同步说明。 - 完整代码。 - **相关问题**:在最后生成。 代码示例: - 使用C语言。 - 包括互斥锁。 参考引用[^1]的“eat”“spit”,我可以用类似命名,但为了清晰,使用enqueuedequeue。 添加引用标识:在描述同步机制时,引用[^1]的“eat”“spit”概念。 最终回答结构: 1. 介绍 2. 数据结构定义 3. 初始化 4. 入队操作(生产者) 5. 出队操作(消费者) 6. 销毁队列 7. 同步机制说明 8. 完整代码(可选或简化) 9. 相关问题</think>### 基于单链表的多生产者消费者消息队列实现 在C语言中实现一个基于单链表的消息队列,适用于生产者(多个线程同时添加消息)消费者(单个线程处理消息)场景,需要确保线程安全。核心是使用单链表存储消息,并通过互斥锁(mutex)同步生产者操作。消费者操作无需额外锁(因为是单消费者),但需注意队列空时的处理。以下实现参考了生产者-消费者模型中的资源管理思想,如引用[^1]中描述的“吃掉”(消费者移除节点)“吐出”(生产者添加节点)的概念。 #### 1. **数据结构定义** 首先定义队列节点队列结构体。队列包含头指针(指向队首)、尾指针(指向队尾),以及一个互斥锁保护生产者入队操作。 ```c #include <stdio.h> #include <stdlib.h> #include <pthread.h> // 队列节点结构体 typedef struct Node { void *data; // 消息数据指针(通用类型) struct Node *next; // 指向下一个节点 } Node; // 队列结构体 typedef struct { Node *head; // 队头指针(消费者从此取消息) Node *tail; // 队尾指针(生产者添加消息) pthread_mutex_t lock; // 互斥锁,保护生产者入队操作 } MessageQueue; ``` #### 2. **初始化队列** 初始化函数创建空队列,并设置互斥锁。 ```c MessageQueue* queue_init() { MessageQueue *q = (MessageQueue*)malloc(sizeof(MessageQueue)); if (q) { q->head = NULL; q->tail = NULL; pthread_mutex_init(&q->lock, NULL); // 初始化互斥锁 } return q; } ``` #### 3. **生产者入队操作(enqueue)** 多个生产者线程调用此函数添加消息。使用互斥锁保护链表操作,确保线程安全。生产者将新节点添加到队尾。 ```c void enqueue(MessageQueue *q, void *data) { Node *new_node = (Node*)malloc(sizeof(Node)); if (!new_node) { perror("Memory allocation failed"); return; } new_node->data = data; new_node->next = NULL; pthread_mutex_lock(&q->lock); // 加锁,保护共享资源 if (q->tail == NULL) { // 队列为空 q->head = new_node; q->tail = new_node; } else { // 队列非空,添加到队尾 q->tail->next = new_node; q->tail = new_node; } pthread_mutex_unlock(&q->lock); // 解锁 // 生产者“吐出”新节点,类似引用[^1]中的资源添加逻辑 } ``` #### 4. **消费者出队操作(dequeue)** 单消费者线程调用此函数取出消息。消费者从队头移除节点,无需加锁(单消费者场景),但需处理队列空的情况。 ```c void* dequeue(MessageQueue *q) { if (q->head == NULL) { // 队列为空 return NULL; } Node *temp = q->head; void *data = temp->data; q->head = q->head->next; if (q->head == NULL) { // 移除后队列为空,重置尾指针 q->tail = NULL; } free(temp); // 释放节点内存 // 消费者“吃掉”节点,类似引用[^1]中的资源移除逻辑 return data; } ``` #### 5. **销毁队列** 释放队列资源,包括所有节点互斥锁。 ```c void queue_destroy(MessageQueue *q) { if (q) { pthread_mutex_lock(&q->lock); Node *current = q->head; while (current) { // 遍历释放所有节点 Node *temp = current; current = current->next; free(temp); } pthread_mutex_unlock(&q->lock); pthread_mutex_destroy(&q->lock); // 销毁互斥锁 free(q); } } ``` #### 6. **同步机制说明** - **多生产者安全**:使用互斥锁确保入队操作原子性,避免多个生产者同时修改链表导致数据竞争。 - **单消费者简化**:出队操作无需锁,因为只有一个消费者线程,减少了开销(但需确保消费者线程的独占访问)。 - **资源管理**:入队时生产者“吐出”新节点(添加资源),出队时消费者“吃掉”节点(移除资源),这类似于引用[^1]中的链表管理模式,其中节点在activefree链表间移动以支持并发。 - **性能考虑**:在$n$个生产者场景下,入队操作的平均时间复杂度为$O(1)$(链表追加),出队为$O(1)$(链表头部移除)。但互斥锁可能成为瓶颈,可通过优化锁粒度或使用无锁数据结构提升性能[^2][^3]。 #### 7. **完整示例用法** 以下是一个简单测试代码,展示多生产者消费者的工作流程。 ```c #include <unistd.h> // 生产者线程函数 void* producer_thread(void *arg) { MessageQueue *q = (MessageQueue*)arg; int *data = malloc(sizeof(int)); *data = rand() % 100; // 生成随机消息 enqueue(q, data); printf("Producer added: %d\n", *data); return NULL; } // 消费者线程函数 void* consumer_thread(void *arg) { MessageQueue *q = (MessageQueue*)arg; while (1) { int *data = (int*)dequeue(q); if (data) { printf("Consumer processed: %d\n", *data); free(data); // 释放消息数据 } else { usleep(100000); // 队列空时短暂休眠 } } return NULL; } int main() { MessageQueue *q = queue_init(); pthread_t producers[3], consumer; // 创建3个生产者线程 for (int i = 0; i < 3; i++) { pthread_create(&producers[i], NULL, producer_thread, q); } // 创建1个消费者线程 pthread_create(&consumer, NULL, consumer_thread, q); // 等待线程结束(简化示例,实际需信号量控制) for (int i = 0; i < 3; i++) { pthread_join(producers[i], NULL); } pthread_join(consumer, NULL); queue_destroy(q); return 0; } ``` ### 相关问题 1. 如何优化多生产者消费者队列的性能以降低锁竞争? 2. 在消息队列中如何处理队列满或空时的阻塞唤醒机制? 3. 这种队列在实时系统或网络框架中的具体应用场景有哪些? 4. 基于引用[^3],比较单链表队列与循环队列在并发场景下的优缺点? [^1]: 将节点从active_head链表移动到free_head链表,有点像生产者消费者模型中的消费者,吃掉资源后,就要把这个节点放置到空闲链表,让生产者能够继续生产数据,所以这两个函数我起名eat、spit,意为吃掉吐,希望你们不要觉得很怪异。 [^2]: C语言实现数据结构之队列。 [^3]: 栈队列(C语言)。 [^4]: 队列,是一种先进先出(first in first out 简称FIFO)的数据结构。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值