基于 C 语言的线程安全的图像缓冲队列
线程安全的图像缓冲队列 1. 介绍 2. 关键代码 2.1 数据结构 2.2 入队出队核心函数 3. 内存管理注意事项 3.1 c_mat_t 和 mat_data 的生命周期 3.2 队列销毁时的内存清理： 4. 总结 线程安全的图像缓冲队列 在这篇博客中，笔者将探讨一个最近实现的基于 C 语言的线程安全图像缓冲循环队列，重点分析其内存管理的细节和注意事项。该队列用于在图像处理后入队，供后续 AI 推理时出队使用，特别适用于实时视频处理或 AI 推理流水线。以下笔者将聚焦内存管理的实现细节、潜在风险及优化建议。 1. 介绍 该图像缓冲队列是一个固定大小的线程安全循环队列，基于 OpenCV 的 Mat 结构存储图像数据，采用生产者-消费者模型。内存管理是笔者重点考虑的细节，需要确保队列在高负载场景下的稳定性和高效性： 生产者：预处理图像（如调整大小、归一化）并入队。 消费者：出队图像并送入 AI 模型进行推理。 队列的关键特性包括： 使用 POSIX 线程（ pthread ）的互斥锁和条件变量实现线程安全。 通过循环缓冲区优化内存使用。 支持图像元数据（名称、路径）和 OpenCV 图像数据的管理。 2. 关键代码 2.1 数据结构 c_mat_t：OpenCV Mat 的封装，存储图像数据及其元数据。 typedef struct c_mat { unsigned char* mat_data; int mat_data_size; int mat_width; int mat_height; int mat_channels; int mat_type; } c_mat_t; 内存管理要点：mat_data 是一个指针，指向动态分配的实际图像数据。调用者需负责分配和释放内存，队列本身不管理 mat_data 的生命周期。 ImageInfo 是存储图像元数据和 c_mat_t 指针; ImageQueue 是循环队列结构，包含 ImageInfo 数组和同步原语: typedef struct { char name[MAX_NAME_LEN]; char path[MAX_NAME_LEN]; c_mat_t *img; } ImageInfo; typedef struct { ImageInfo items[MAX_QUEUE_SIZE]; int front; int rear; int size; pthread_mutex_t mutex; pthread_cond_t not_full; pthread_cond_t not_empty; } ImageQueue; 内存管理要点：items 是一个固定大小的数组（MAX_QUEUE_SIZE = 10），在栈或全局内存中分配，无需动态分配。mutex 和条件变量由 POSIX 线程库管理。 2.2 入队出队核心函数 从该队列设计上讲：调用者手动管理每一张图片的内存创建与释放，因此初始化队列和销毁队列的设计比较简单且常规，不再赘述。 入队图像： int enqueue(ImageQueue *queue, const char *name, const char *path, const c_mat_t *img) { pthread_mutex_lock(&queue->mutex); while (queue->size == MAX_QUEUE_SIZE) { pthread_cond_wait(&queue->not_full, &queue->mutex); } queue->rear = (queue->rear + 1) % MAX_QUEUE_SIZE; strncpy(queue->items[queue->rear].name, name, MAX_NAME_LEN - 1); strncpy(queue->items[queue->rear].path, path, MAX_NAME_LEN - 1); queue->items[queue->rear].img = img; queue->size++; pthread_cond_signal(&queue->not_empty); pthread_mutex_unlock(&queue->mutex); return 0; } 内存管理： name 和 path 使用 strncpy 复制到固定大小的字符数组，MAX_NAME_LEN - 1 确保留空间给终止符，避免缓冲区溢出。 img 指针直接赋值，队列不负责分配或释放 c_mat_t 或其 mat_data，该操作由创建实际参数时负责。 注意：笔者调用前已经确保 img 和 mat_data 在入队期间保持有效，且在队列使用期间不被释放。 出队图像: int dequeue(ImageQueue *queue, ImageInfo *info) { pthread_mutex_lock(&queue->mutex); while (queue->size == 0) { pthread_cond_wait(&queue->not_empty, &queue->mutex); } *info = queue->items[queue->front]; queue->front = (queue->front + 1) % MAX_QUEUE_SIZE; queue->size--; pthread_cond_signal(&queue->not_full); pthread_mutex_unlock(&queue->mutex); return 0; } 内存管理： info 接收 ImageInfo 的副本，name 和 path 的字符数组被复制，img 指针被复制。 注意：出队后，info->img 指向的 c_mat_t 和 mat_data 仍由调用者管理。此时笔者通常会才使用完ImageInfo *info后（如 AI 推理）手动释放，如下所示：（这样的好处在于只是用该张图片的原来存储的堆空间，便于内存管理，以及避免深拷贝带来的时间成本） if (info.img->mat_data) { image_free(info.img); info.img->mat_data = NULL; } 3. 内存管理注意事项 3.1 c_mat_t 和 mat_data 的生命周期 c_mat_t和其 mat_data 由调用者分配和释放，队列仅存储指针。这要求调用者在入队和出队时确保内存的有效性。这意味着调用者需要在调用入队前分配，出队后释放，这一点很重要。 3.2 队列销毁时的内存清理： destroy_queue 仅释放 POSIX 资源，未清理 items 中残留的 img 指针。 问题：如果队列销毁时仍有图像未出队，c_mat_t 和 mat_data 可能泄漏。 改进办法： 在 destroy_queue 中遍历 items，释放有效的 img 和 mat_data（需调用者提供释放函数）。 或者记录所有入队的 c_mat_t，在销毁时统一释放。 4. 总结 该图像缓冲队列通过固定大小的循环缓冲区和线程同步机制，实现了高效的图像数据管理。然而，内存管理的细节至关重要，尤其是在 c_mat_t 和 mat_data 的生命周期管理、字符数组截断、以及队列销毁时的清理等方面。遵循上述注意事项和优化建议，可以显著提高队列的稳定性和可维护性。