如何实现不阻塞程序执行的定时器并触发回调函数?
实现非阻塞定时器触发回调的C语言方案
你需要的是非阻塞式的定时器:调用后不阻塞主线程,指定时长到期时自动触发回调函数。以下是两种实用的实现方案:
方案一:使用POSIX定时器API(Linux/Unix专属)
POSIX提供的timer_create系列接口是系统级的非阻塞定时器,支持通过线程或信号触发回调,不会阻塞主线程。下面是贴合你需求的封装实现:
#include <stdio.h> #include <signal.h> #include <time.h> #include <stdlib.h> // 定义定时器类型 typedef timer_t some_timer_t; // 回调函数 void callback_fn(union sigval arg) { puts("Time is over!"); } // 创建并启动定时器(单位:毫秒) some_timer_t create_timer(unsigned int ms) { some_timer_t timer; struct sigevent sev; struct itimerspec its; // 配置定时器:到期后创建新线程执行回调(避免信号回调的安全限制) sev.sigev_notify = SIGEV_THREAD; sev.sigev_notify_function = callback_fn; sev.sigev_value.sival_ptr = &timer; sev.sigev_notify_attributes = NULL; // 创建定时器 if (timer_create(CLOCK_MONOTONIC, &sev, &timer) == -1) { perror("timer_create failed"); exit(EXIT_FAILURE); } // 设置定时参数:首次触发在ms毫秒后,仅触发一次 its.it_value.tv_sec = ms / 1000; its.it_value.tv_nsec = (ms % 1000) * 1000000; its.it_interval.tv_sec = 0; its.it_interval.tv_nsec = 0; // 启动定时器 if (timer_settime(timer, 0, &its, NULL) == -1) { perror("timer_settime failed"); exit(EXIT_FAILURE); } return timer; } // 关联回调(本方案中已在create_timer完成关联,此函数可留空或做定时器管理) void when_timer_is_over(some_timer_t timer) { // 无需额外操作,回调已通过sigevent绑定 } int main(void) { some_timer_t timer = create_timer(500); // 500毫秒定时器 // 主线程可正常执行其他任务,不会阻塞 puts("Main thread is running..."); // 保持主线程运行,否则程序会直接退出,无法看到回调触发 while (1) { sleep(1); puts("Main thread is still working..."); } return 0; }
说明
- 使用
CLOCK_MONOTONIC时钟,避免系统时间修改影响定时器精度 - 采用
SIGEV_THREAD模式,让系统自动创建线程执行回调,可安全调用标准库函数(如puts) - 编译时需链接实时库:
gcc timer.c -o timer -lrt
方案二:后台线程实现(跨平台)
如果需要跨平台支持(如Windows),可以用后台线程模拟定时器:线程内等待指定时长后触发回调,主线程正常运行。
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> // 定时器数据结构 typedef struct { unsigned int ms; void (*callback)(void); } timer_data_t; // 定义定时器类型 typedef timer_data_t* some_timer_t; // 回调函数 void callback_fn() { puts("Time is over!"); } // 定时器线程函数:等待时长后触发回调 void* timer_thread(void* arg) { timer_data_t* data = (timer_data_t*)arg; // 将毫秒转换为微秒,用usleep等待 unsigned int usec = data->ms * 1000; usleep(usec); // 触发回调 data->callback(); // 释放内存 free(data); return NULL; } // 创建定时器(仅保存时长,暂不启动) some_timer_t create_timer(unsigned int ms) { some_timer_t timer = malloc(sizeof(timer_data_t)); if (!timer) { perror("malloc failed"); exit(EXIT_FAILURE); } timer->ms = ms; timer->callback = NULL; return timer; } // 关联回调并启动定时器 void when_timer_is_over(some_timer_t timer, void (*callback)(void)) { timer->callback = callback; pthread_t tid; // 创建后台线程 if (pthread_create(&tid, NULL, timer_thread, timer) != 0) { perror("pthread_create failed"); free(timer); exit(EXIT_FAILURE); } // 分离线程,避免主线程需要调用pthread_join pthread_detach(tid); } int main(void) { some_timer_t timer = create_timer(500); // 500毫秒定时器 when_timer_is_over(timer, callback_fn); // 主线程执行其他任务,不阻塞 puts("Main thread is running..."); while (1) { sleep(1); puts("Main thread is still working..."); } return 0; }
说明
- 跨平台性好,只要支持POSIX线程(Windows可替换为
CreateThread) - 编译时需链接线程库:
gcc timer.c -o timer -lpthread - 主线程必须保持运行,否则程序终止后回调无法触发
通用注意事项
- 你的原示例中
main直接return 0,程序会立即退出,回调根本没机会执行,所以必须让主线程持续运行(比如加循环) - 如果需要重复触发定时器,可修改方案中的定时参数(方案一修改
it_interval,方案二让线程循环等待)
内容的提问来源于stack exchange,提问作者AggelosT
相关产品推荐
相关产品推荐

