基于C语言的模块间音频缓冲区内存管理及单生产者单消费者队列架构咨询
单生产者单消费者Int队列的C语言实现(适配音频缓冲区场景)
针对你在C语言音频缓冲区内存管理中需要实现的单生产者单消费者Int队列,我给你一套完整的、线程安全的实现方案,完全适配你的场景需求:
核心设计思路
单生产者单消费者模型是音频这类流式处理场景的最优选择之一——既保证线程安全,又避免了多生产者/消费者场景下的复杂同步开销。我们用pthread的互斥锁(mutex)保护队列的临界区操作,用条件变量(condition variable)实现生产者和消费者的同步等待。
完整代码实现
1. 数据结构定义
首先定义队列的节点和控制结构,支持有界队列(适合音频缓冲区的内存限制):
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> // Int队列节点 typedef struct QueueNode { int data; struct QueueNode* next; } QueueNode; // 队列控制结构体:包含同步原语和状态信息 typedef struct IntQueue { QueueNode* head; QueueNode* tail; pthread_mutex_t mutex; pthread_cond_t not_empty; // 消费者等待:队列非空 pthread_cond_t not_full; // 生产者等待:队列未满(有界队列用) int capacity; // 队列最大容量(0表示无界) int size; // 当前元素数量 } IntQueue;
2. 队列初始化与销毁
负责创建队列并初始化同步原语,以及销毁时释放所有资源:
// 初始化队列,指定容量(音频场景建议设置合理的有界容量) IntQueue* init_int_queue(int capacity) { IntQueue* queue = (IntQueue*)malloc(sizeof(IntQueue)); if (!queue) { perror("Failed to allocate queue"); return NULL; } queue->head = queue->tail = NULL; queue->capacity = capacity; queue->size = 0; // 初始化互斥锁和条件变量 if (pthread_mutex_init(&queue->mutex, NULL) != 0) { perror("Mutex init failed"); free(queue); return NULL; } if (pthread_cond_init(&queue->not_empty, NULL) != 0) { perror("Not-empty cond init failed"); pthread_mutex_destroy(&queue->mutex); free(queue); return NULL; } if (pthread_cond_init(&queue->not_full, NULL) != 0) { perror("Not-full cond init failed"); pthread_cond_destroy(&queue->not_empty); pthread_mutex_destroy(&queue->mutex); free(queue); return NULL; } return queue; } // 销毁队列,释放所有内存和同步资源 void destroy_int_queue(IntQueue* queue) { if (!queue) return; // 先加锁保护,避免销毁时还有线程访问 pthread_mutex_lock(&queue->mutex); QueueNode* temp; while (queue->head) { temp = queue->head; queue->head = queue->head->next; free(temp); } pthread_mutex_unlock(&queue->mutex); pthread_mutex_destroy(&queue->mutex); pthread_cond_destroy(&queue->not_empty); pthread_cond_destroy(&queue->not_full); free(queue); }
3. 生产者线程实现
负责生成(或从音频模块获取)Int数据并加入队列:
void* producer_thread(void* arg) { IntQueue* queue = (IntQueue*)arg; int audio_data; // 实际场景替换为从音频模块获取的int类型数据 // 示例:生成10条测试数据,实际替换为你的音频数据生产逻辑 for (int i = 0; i < 10; i++) { audio_data = i; pthread_mutex_lock(&queue->mutex); // 有界队列下,等待队列有空闲空间 while (queue->capacity > 0 && queue->size >= queue->capacity) { printf("Producer: Queue full, waiting...\n"); pthread_cond_wait(&queue->not_full, &queue->mutex); } // 创建新节点并加入队列 QueueNode* new_node = (QueueNode*)malloc(sizeof(QueueNode)); if (!new_node) { perror("Node allocation failed"); pthread_mutex_unlock(&queue->mutex); break; } new_node->data = audio_data; new_node->next = NULL; if (queue->tail == NULL) { // 队列为空时的特殊处理 queue->head = queue->tail = new_node; } else { queue->tail->next = new_node; queue->tail = new_node; } queue->size++; printf("Producer: Added data %d, queue size: %d\n", audio_data, queue->size); // 通知消费者队列已有数据 pthread_cond_signal(&queue->not_empty); pthread_mutex_unlock(&queue->mutex); // 模拟音频数据生成间隔,实际根据你的模块速度调整 usleep(500000); } printf("Producer thread finished\n"); return NULL; }
4. 消费者线程实现(对应你给出的核心逻辑)
负责从队列取出数据并处理(比如写入音频缓冲区):
void* consumer_thread(void* arg) { IntQueue* queue = (IntQueue*)arg; QueueNode* temp_node; int processed_data; // 实际场景可以设置退出flag,这里用循环模拟持续消费 while (1) { pthread_mutex_lock(&queue->mutex); // 等待队列非空(必须用while,防止虚假唤醒) while (queue->size == 0) { printf("Consumer: Queue empty, waiting...\n"); pthread_cond_wait(&queue->not_empty, &queue->mutex); } // 取出队首元素 temp_node = queue->head; processed_data = temp_node->data; queue->head = queue->head->next; if (queue->head == NULL) { queue->tail = NULL; } queue->size--; printf("Consumer: Processed data %d, queue size: %d\n", processed_data, queue->size); // 释放节点内存(音频场景建议用内存池复用节点,减少malloc开销) free(temp_node); // 有界队列下,通知生产者队列有空闲空间 if (queue->capacity > 0) { pthread_cond_signal(&queue->not_full); } // 这里添加你的音频数据处理逻辑,比如写入缓冲区或传给下一个模块 usleep(1000000); // 模拟处理时间 pthread_mutex_unlock(&queue->mutex); } printf("Consumer thread finished\n"); return NULL; }
5. 主函数示例(启动线程)
int main() { // 初始化容量为5的有界队列,根据你的音频缓冲区大小调整 IntQueue* audio_queue = init_int_queue(5); if (!audio_queue) { return 1; } pthread_t producer_tid, consumer_tid; // 创建生产者线程 if (pthread_create(&producer_tid, NULL, producer_thread, (void*)audio_queue) != 0) { perror("Failed to create producer thread"); destroy_int_queue(audio_queue); return 1; } // 创建消费者线程 if (pthread_create(&consumer_tid, NULL, consumer_thread, (void*)audio_queue) != 0) { perror("Failed to create consumer thread"); pthread_cancel(producer_tid); destroy_int_queue(audio_queue); return 1; } // 等待生产者线程结束 pthread_join(producer_tid, NULL); // 优雅终止消费者线程(实际场景建议用退出flag替代pthread_cancel) pthread_cancel(consumer_tid); pthread_join(consumer_tid, NULL); destroy_int_queue(audio_queue); return 0; }
关键注意事项(音频场景必看)
- 必须用while循环检查条件:你代码里的
while (!condition)是正确的!pthread_cond_wait可能会被虚假唤醒,所以必须重新确认条件是否满足,不能用if判断。 - 内存池优化:音频场景对实时性要求高,频繁malloc/free会导致性能波动,建议预先创建一个节点内存池,复用节点,减少内存碎片和分配开销。
- 有界队列设置:一定要设置合理的队列容量,避免内存无限增长,容量可以根据音频采样率、单帧数据大小、延迟要求来计算。
- 优雅退出逻辑:示例中用了
pthread_cancel,生产环境建议用一个受互斥锁保护的全局退出flag,让线程主动检查并退出,避免资源泄漏。 - 互斥锁范围最小化:尽量缩短持有互斥锁的时间,比如把音频数据的处理逻辑放在锁外面,只在访问队列结构时加锁,提升性能。
内容的提问来源于stack exchange,提问作者Nishant Bijani A
相关产品推荐
相关产品推荐

