如何在被x_io事件循环阻塞的线程中执行外部触发的函数
主线程使用main_io异步库,子线程使用x_io异步库。需要从主线程调用子线程中的func_x函数,但子线程启动后会进入x_io_lib_loop_forever()的永久阻塞循环,导致func_x无法被执行。
简化代码如下:
void bla_x() { // IO操作完成时触发此函数,由x_io_lib_loop_forever()内部调用 main_io_lib_call_func_in_main(callback_func_main_process); } void init_x() { // 初始化x_io库 x_io_lib_loop_forever(); } void callback_func_main_process() { main_io_lib_call_func_in_threadX(func_x); } void start_threadX() { main_io_lib_register_handler(init_x); main_io_lib_register_handler(func_x); main_io_lib_loop_forever(); } int main() { pthread_create(start_thread()); main_io_lib_call_func_in_thread(init_x); main_io_lib_loop_forever(); }
具体场景:主线程创建子线程并执行init_x(),子线程初始化后进入x_io的永久事件循环。当子线程的IO操作完成触发bla_x()后,会回调主线程的callback_func_main_process(),进而尝试调用子线程的func_x(),但由于x_io_lib_loop_forever()阻塞,func_x无法执行。需要找到暂停x_io循环并执行func_x的方法,或是更优的设计方案。
方法1:利用x_io库的线程任务投递接口(优先尝试)
大多数异步IO库都会提供线程内任务投递的API,允许其他线程把函数提交到事件循环所在线程执行。如果x_io有类似x_io_lib_post_task(func_x, NULL)这样的接口,直接替换main_io_lib_call_func_in_threadX(func_x)即可。
修改后的callback_func_main_process:
void callback_func_main_process() { // 直接把func_x投递到x_io的事件循环线程执行 x_io_lib_post_task(func_x, NULL); }
这种方式不需要修改事件循环的阻塞逻辑,是最优雅的实现。
方法2:给x_io的事件循环添加中断触发点
如果x_io没有内置任务投递功能,可以用管道/事件fd作为触发源,把它注册到x_io的事件循环中,当主线程需要调用func_x时,向管道写数据,触发事件循环执行回调,在回调里调用func_x。
步骤如下:
- 在
init_x中创建管道,并把读端注册到x_io的可读事件:
int pipe_fd[2]; void pipe_read_callback(int fd) { char buf[1]; read(fd, buf, 1); // 清空管道数据 func_x(); // 执行目标函数 } void init_x() { pipe(pipe_fd); // 把管道读端注册到x_io的可读事件,触发时调用pipe_read_callback x_io_lib_register_read_handler(pipe_fd[0], pipe_read_callback); x_io_lib_loop_forever(); }
- 修改主线程的调用逻辑,通过写管道触发执行:
void callback_func_main_process() { char buf[1] = {0}; write(pipe_fd[1], buf, 1); // 写数据触发读事件 }
注意要保证pipe_fd是全局变量或者能被主线程访问到的变量。
方法3:重构子线程的初始化逻辑
避免init_x直接调用x_io_lib_loop_forever()阻塞,而是把事件循环放到子线程的主函数中,先完成初始化再启动循环,同时用线程同步机制(比如条件变量)等待初始化完成。
修改后的子线程启动代码:
pthread_cond_t cond_init_done = PTHREAD_COND_INITIALIZER; pthread_mutex_t mutex_init = PTHREAD_MUTEX_INITIALIZER; int x_init_finished = 0; void* threadX_main(void* arg) { // 初始化x_io库 x_io_lib_init(); // 注册需要的事件 handler x_io_lib_register_some_handler(bla_x); // 标记初始化完成,通知主线程 pthread_mutex_lock(&mutex_init); x_init_finished = 1; pthread_cond_signal(&cond_init_done); pthread_mutex_unlock(&mutex_init); // 启动x_io的事件循环 x_io_lib_loop_forever(); return NULL; } int main() { pthread_t tid; pthread_create(&tid, NULL, threadX_main, NULL); // 等待子线程初始化完成 pthread_mutex_lock(&mutex_init); while (!x_init_finished) { pthread_cond_wait(&cond_init_done, &mutex_init); } pthread_mutex_unlock(&mutex_init); // 主线程启动自己的事件循环 main_io_lib_loop_forever(); }
这种方式能保证子线程的事件循环正常运行,同时如果x_io支持动态添加任务,后续投递func_x的逻辑和方法1一致。
方法4:替换x_io_lib_loop_forever()为非阻塞循环
如果x_io允许,可以手动控制事件循环的迭代,用x_io_lib_loop_once()代替永久阻塞,在循环中检查是否有需要执行的任务:
// 全局变量标记是否需要执行func_x int need_run_func_x = 0; pthread_mutex_t mutex_task = PTHREAD_MUTEX_INITIALIZER; void init_x() { // 初始化操作 while (1) { x_io_lib_loop_once(100); // 单次循环,超时100ms pthread_mutex_lock(&mutex_task); if (need_run_func_x) { func_x(); need_run_func_x = 0; } pthread_mutex_unlock(&mutex_task); } } void callback_func_main_process() { pthread_mutex_lock(&mutex_task); need_run_func_x = 1; pthread_mutex_unlock(&mutex_task); }
这种方式需要修改事件循环的运行逻辑,适合没有其他投递接口的场景,但会增加少量的循环开销。
内容的提问来源于stack exchange,提问作者匿名用户

