摄像头采集线程+编码线程:
比如一个智能摄像头项目里,通常会有:
采集线程:不断从摄像头采集图像帧
编码线程:拿到图像帧后进行H264编码
上传线程:把编码处理后的数据上传到服务器
其中,”采集线程“->”编码线程“最典型的做法就是:
共享一块缓冲区,
采集线程把最新帧写进去,
编码线程等待有”新数据“的通知,
编码线程取走数据后进行处理。
这就是典型的:
共享内存
互斥锁mutex
条件变量cond
共享内存:
线程共享进程地址空间,所以全局变量、堆内存、静态变量都可以被多个线程共同访问。
为什么需要同步?
因为多个线程同时访问共享变量时会出问题:
如:线程A正在写
线程B同时在读
这样同时访问共享变量,可能读到的数据就不完整。
所以要加同步机制。
头文件:
#include <pthread.h>互斥锁:
pthread_mutex_t mutex; //互斥锁变量pthread_mutex_init(&mutex, NULL); //互斥锁初始化pthread_mutex_lock(&mutex); //上锁pthread_mutex_unlock(&mutex); //解锁pthread_mutex_destroy(&mutex); //销毁作用:
保护临界区,
同一时刻只允许一个线程操作共享数据。
条件变量:
pthread_cond_t cond; pthread_cond_init(&cond, NULL); //等待通知初始化pthread_cond_wait(&cond, &mutex); //等待通知线程pthread_cond_signal(&cond); //通知单个线程pthread_cond_broadcast(&cond); //通知所有线程pthread_cond_destroy(&cond);作用:
A线程等待条件成立 (消费者)
B线程条件满足后发通知 (生产者)
代码演示:单缓冲生产者消费者
功能说明: 模拟一个设备采集线程每秒产生一帧数据,处理线程收到后进行处理。
#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <unistd.h>#include <string.h>/* * 模拟一帧图像数据 * 实际项目里可能是一块图像buffer、传感器采样值、音频帧等 */typedef struct { int frame_id; // 帧号 char payload[64]; // 模拟数据内容} Frame;/* 共享缓冲区:采集线程写,处理线程读 */static Frame g_frame;/* 标记缓冲区里是否有新数据 * 0: 没有新数据 * 1: 有新数据等待处理 */static int g_has_new_frame = 0;/* 互斥锁:保护共享变量 g_frame 和 g_has_new_frame */static pthread_mutex_t g_mutex;/* 条件变量:用于“通知有新数据了” */static pthread_cond_t g_cond;/* * 采集线程:模拟摄像头/传感器采集数据 */void *capture_thread(void *arg){ int i;for (i = 1; i <= 5; i++) { sleep(1); // 模拟采集耗时 pthread_mutex_lock(&g_mutex); /* * 如果上一帧还没被处理,这里简单等待 * 实际项目中也可以选择覆盖旧帧,取决于业务需求 */while (g_has_new_frame == 1) { pthread_mutex_unlock(&g_mutex); usleep(1000); // 稍等1ms,避免纯忙等太激进 pthread_mutex_lock(&g_mutex); } /* 写入新数据 */ g_frame.frame_id = i; snprintf(g_frame.payload, sizeof(g_frame.payload),"frame_data_%d", i); g_has_new_frame = 1;printf("[capture] produce frame_id=%d, payload=%s\n", g_frame.frame_id, g_frame.payload); /* 通知处理线程:有新数据了 */ pthread_cond_signal(&g_cond); pthread_mutex_unlock(&g_mutex); }return NULL;}/* * 处理线程:等待新帧到来,然后消费 */void *process_thread(void *arg){ int count = 0;while (count < 5) { pthread_mutex_lock(&g_mutex); /* * 这里必须用 while,不能用 if * 原因: * 1. 条件变量可能“虚假唤醒” * 2. 被唤醒时条件未必还成立 */while (g_has_new_frame == 0) { pthread_cond_wait(&g_cond, &g_mutex); } /* 取出共享数据 */ Frame local_frame; memcpy(&local_frame, &g_frame, sizeof(Frame)); /* 标记这帧已经被消费 */ g_has_new_frame = 0; pthread_mutex_unlock(&g_mutex); /* 在锁外处理,减少锁占用时间 */printf("[process] consume frame_id=%d, payload=%s\n", local_frame.frame_id, local_frame.payload); count++; }return NULL;}int main(void){ pthread_t tid_capture; pthread_t tid_process; pthread_mutex_init(&g_mutex, NULL); pthread_cond_init(&g_cond, NULL); pthread_create(&tid_capture, NULL, capture_thread, NULL); pthread_create(&tid_process, NULL, process_thread, NULL); pthread_join(tid_capture, NULL); pthread_join(tid_process, NULL); pthread_mutex_destroy(&g_mutex); pthread_cond_destroy(&g_cond);return 0;}编译:
gcc test.c -o test -lpthread运行:
tammy@ubuntu:~/cprogramer/thread_pro$ gcc 01_mutex_cond.c -o 01_mutex_cond -lpthreadtammy@ubuntu:~/cprogramer/thread_pro$ ls01_mutex_cond 01_mutex_cond.ctammy@ubuntu:~/cprogramer/thread_pro$ ./01_mutex_cond[capture] produce frame_id=1, payload=frame_data_1[process] consume frame_id=1, payload=frame_data_1[capture] produce frame_id=2, payload=frame_data_2[process] consume frame_id=2, payload=frame_data_2[capture] produce frame_id=3, payload=frame_data_3[process] consume frame_id=3, payload=frame_data_3[capture] produce frame_id=4, payload=frame_data_4[process] consume frame_id=4, payload=frame_data_4[capture] produce frame_id=5, payload=frame_data_5[process] consume frame_id=5, payload=frame_data_5实际工程里,更稳的是:双缓冲 / 环形缓冲
buffer A 正在给消费者读
buffer B 给生产者写
写完后交换索引
好处:
降低“读写同一块内存”的冲突
更容易保证数据完整
多个 frame slot
生产者写 tail
消费者读 head
用计数器 + 条件变量/信号量同步
适合:
有多个消费者
想缓存多帧
想减少丢帧