如何在C异步编程(Vimba API场景)中管理全局变量?
问题
我正在使用Vimba.c API,基于文档中的AsynchronousGrab示例做二次开发,已经把程序改成了用户触发模式:主程序在前台运行,AsynchronousGrab在后台持续传输相机流,当main发送触发信号时,异步抓取模块会捕获一帧并处理,之后继续维持流传输。目前已经修改AsynchronousGrab.c,为捕获的帧添加了自定义处理步骤并打印结果。
主程序program.c的核心循环代码如下:
while (1) { // flush(stdin); printf("\nInsert <c> to capture a frame or <e> to stop acquisition and press ENTER\n\n"); char_in = getchar(); if (char_in == 'c') { err = VmbFeatureCommandRun(_cameraHandle, "TriggerSoftware"); if (VmbErrorSuccess == err) { printf("Software Trigger sent\n"); } else { printf("Error: %s\n", ErrorCodeToMessage(err)); } } else if (char_in == 'e') { printf("Initializing closing routine\n"); break; } else { printf("Invalid input\n"); } char_in = getchar(); //consumes remaining '\n' from stdin }
现在想新增保存功能,添加了's'指令分支:
else if (char_in == 's') { /* some code to signal the save*/ err = VmbFeatureCommandRun(_cameraHandle, "TriggerSoftware"); if (VmbErrorSuccess == err) { printf("Software Trigger sent. Frame %d will be saved.\n", frameCounter); } else { printf("Error: %s\n", ErrorCodeToMessage(err)); } }
我的困惑是:不知道怎么给另一个.c文件发送标记来实现条件保存。最初考虑传引用,但VmbFeatureCommandRun是Vimba库函数,没法改参数;后来想过全局变量+extern声明,但异步场景下如果前一帧还没处理完就发新触发,会导致标记值混乱。于是想到用3个标记数组,搭配全局帧ID数组来匹配标记、处理结果和文件名,但担心太复杂。想问:
- 是否应该创建两个全局数组?
- 如果可行,是不是只需要在program.c里声明全局变量,在AsynchronousGrab.c里用extern引用,然后通过帧ID匹配所有内容?
解决方案
不推荐用多全局数组,太冗余
你的思路方向是对的,但用3个数组没必要,核心是把「是否要保存」的标记和对应的帧ID绑定,避免异步场景下的竞态问题。下面给你两种更简洁的实现方式:
方式1:用「线程安全的队列」存待保存的帧ID
这是最稳妥的方案,完全避免全局变量的竞态:
- 在program.c里创建一个线程安全的队列(用互斥锁+链表实现,或者系统提供的线程安全队列均可)
- 当用户输入's'触发时,把当前的
frameCounter(要保存的帧ID)推入队列,再发送软件触发 - 在AsynchronousGrab的帧回调函数里,每处理完一帧,就检查队列:如果当前帧的ID和队列头部的ID匹配,就执行保存操作,然后弹出队列头部
- 注意:队列操作必须加锁,防止主程序和异步回调同时读写
示例伪代码:
// program.c 全局声明(或封装成结构体) #include <pthread.h> #include <queue> std::queue<int> saveFrameQueue; pthread_mutex_t queueMutex = PTHREAD_MUTEX_INITIALIZER; // 主程序's'分支 else if (char_in == 's') { err = VmbFeatureCommandRun(_cameraHandle, "TriggerSoftware"); if (VmbErrorSuccess == err) { pthread_mutex_lock(&queueMutex); saveFrameQueue.push(frameCounter); pthread_mutex_unlock(&queueMutex); printf("Software Trigger sent. Frame %d will be saved.\n", frameCounter); frameCounter++; } else { printf("Error: %s\n", ErrorCodeToMessage(err)); } } // AsynchronousGrab.c 帧回调里的检查逻辑 void FrameCallback(VmbFrame_t* pFrame) { // 先处理帧 ProcessFrame(pFrame); // 检查是否需要保存 pthread_mutex_lock(&queueMutex); if (!saveFrameQueue.empty() && saveFrameQueue.front() == pFrame->frameID) { SaveFrame(pFrame); saveFrameQueue.pop(); } pthread_mutex_unlock(&queueMutex); // 把帧还给相机 VmbFrameQueueCapture(_cameraHandle, pFrame, FrameCallback); }
方式2:用「原子变量+帧ID映射」简化实现
如果不想搞队列,也可以用原子变量+一个小的缓存数组(存最近几个待保存的帧ID),适合触发频率不高的场景:
- 在program.c里声明一个全局的原子布尔数组(或普通数组加互斥锁),数组索引对应帧ID的模值,值表示是否需要保存
- 触发's'时,把对应帧ID模值的数组位置设为
true,发送触发 - 帧回调里,处理完帧后检查当前帧ID模值对应的数组值,如果是
true就保存,然后设回false
示例伪代码:
// program.c 全局声明 #include <stdatomic.h> #define MAX_PENDING_FRAMES 10 atomic_bool saveFlags[MAX_PENDING_FRAMES] = {false}; int currentFrameID = 0; // 主程序's'分支 else if (char_in == 's') { err = VmbFeatureCommandRun(_cameraHandle, "TriggerSoftware"); if (VmbErrorSuccess == err) { int targetID = currentFrameID % MAX_PENDING_FRAMES; atomic_store(&saveFlags[targetID], true); printf("Software Trigger sent. Frame %d will be saved.\n", currentFrameID); currentFrameID++; } else { printf("Error: %s\n", ErrorCodeToMessage(err)); } } // AsynchronousGrab.c 里用extern引用 extern atomic_bool saveFlags[]; extern int currentFrameID; void FrameCallback(VmbFrame_t* pFrame) { ProcessFrame(pFrame); int checkID = pFrame->frameID % MAX_PENDING_FRAMES; if (atomic_load(&saveFlags[checkID])) { SaveFrame(pFrame); atomic_store(&saveFlags[checkID], false); } VmbFrameQueueCapture(_cameraHandle, pFrame, FrameCallback); }
对你疑问的解答
- 不需要两个全局数组:不管用队列还是原子数组,核心都是绑定帧ID和保存标记,一个结构就能搞定
- 全局变量的extern引用是可行的:只要确保全局变量在program.c里定义,AsynchronousGrab.c里用
extern声明即可,但必须加线程同步(互斥锁/原子操作),否则异步场景下会出现数据混乱
内容的提问来源于stack exchange,提问作者ATSlooking4things
相关产品推荐
相关产品推荐

