如何在单个进程中实现多个同类型C++实时定时器回调?
我之前在做Linux和Android应用开发时刚好碰到过这个问题,setitimer的单定时器限制确实挺头疼的。针对你的需求——多实时超时任务、跨Ubuntu和Android,给你几个实用的解决方案:
方案1:手动维护定时器链表 + 单个setitimer模拟多任务
这是最基础的兼容方案,完全依赖系统原生的setitimer,不需要额外依赖,适合所有支持setitimer的系统(包括Ubuntu和全版本Android)。
核心思路是:
- 自己维护一个按到期时间排序的任务链表,每个节点存任务的绝对到期时间、回调函数和参数
- 只设置一个
setitimer,超时时间设为链表中最早到期的任务时间 - 每次定时器触发(收到
SIGALRM信号)时,遍历链表执行所有到期的任务,然后更新setitimer为下一个最早到期的任务时间
关键注意点:
- 用绝对时间(比如
clock_gettime(CLOCK_MONOTONIC))计算到期时间,避免累计误差 - 信号处理函数里别直接执行复杂回调(非异步安全函数会出问题),可以用管道通知主线程处理任务
- 链表操作要保证线程安全(如果是多线程环境)
简化代码示例:
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <sys/time.h> #include <time.h> #include <unistd.h> typedef struct TimerTask { struct timespec expire_time; void (*callback)(void*); void* arg; struct TimerTask* next; } TimerTask; static TimerTask* timer_list = NULL; static int pipe_fd[2]; // 添加任务到有序链表 void add_timer_task(long long ms, void (*cb)(void*), void* arg) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); TimerTask* new_task = malloc(sizeof(TimerTask)); // 计算绝对到期时间 new_task->expire_time.tv_sec = now.tv_sec + ms / 1000; long long nsec = now.tv_nsec + (ms % 1000) * 1000000; if (nsec >= 1000000000) { new_task->expire_time.tv_sec += 1; new_task->expire_time.tv_nsec = nsec - 1000000000; } else { new_task->expire_time.tv_nsec = nsec; } new_task->callback = cb; new_task->arg = arg; new_task->next = NULL; // 插入排序到链表(保证链表按到期时间升序) if (!timer_list || timespec_cmp(&new_task->expire_time, &timer_list->expire_time) < 0) { new_task->next = timer_list; timer_list = new_task; } else { TimerTask* curr = timer_list; while (curr->next && timespec_cmp(&new_task->expire_time, &curr->next->expire_time) >= 0) { curr = curr->next; } new_task->next = curr->next; curr->next = new_task; } // 更新setitimer update_setitimer(); } // 更新setitimer为最早到期的任务时间 static void update_setitimer() { struct itimerval itv = {0}; if (!timer_list) { setitimer(ITIMER_REAL, &itv, NULL); return; } struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); long long diff_sec = timer_list->expire_time.tv_sec - now.tv_sec; long long diff_nsec = timer_list->expire_time.tv_nsec - now.tv_nsec; // 处理已经到期的情况 if (diff_sec < 0 || (diff_sec == 0 && diff_nsec < 0)) { diff_sec = 0; diff_nsec = 0; } itv.it_value.tv_sec = diff_sec; itv.it_value.tv_usec = diff_nsec / 1000; setitimer(ITIMER_REAL, &itv, NULL); } // 信号处理函数:通过管道通知主线程处理任务 static void sigalrm_handler(int sig) { write(pipe_fd[1], "1", 1); } // 主线程处理到期任务 void handle_timer_tasks() { char buf[1]; read(pipe_fd[0], buf, 1); struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); TimerTask** curr = &timer_list; while (*curr) { TimerTask* task = *curr; if (timespec_cmp(&task->expire_time, &now) <= 0) { // 移除任务并执行回调 *curr = task->next; task->callback(task->arg); free(task); } else { break; // 链表有序,后续任务未到期 } } update_setitimer(); } // 辅助函数:比较两个timespec static int timespec_cmp(const struct timespec* a, const struct timespec* b) { if (a->tv_sec != b->tv_sec) return a->tv_sec - b->tv_sec; return a->tv_nsec - b->tv_nsec; } // 示例回调 void test_callback(void* arg) { printf("Task %d triggered!\n", *(int*)arg); free(arg); } int main() { pipe(pipe_fd); struct sigaction sa = {0}; sa.sa_handler = sigalrm_handler; sigaction(SIGALRM, &sa, NULL); // 添加测试任务 int* arg1 = malloc(sizeof(int)); *arg1 = 1; add_timer_task(2000, test_callback, arg1); // 2秒后触发 int* arg2 = malloc(sizeof(int)); *arg2 = 2; add_timer_task(1000, test_callback, arg2); // 1秒后触发 while (1) { handle_timer_tasks(); } return 0; }
方案2:使用POSIX定时器(timer_create/timer_settime)
如果你的目标Android版本在API 21及以上(现在主流设备都满足),POSIX定时器是更省心的选择——系统原生支持多个独立的实时定时器,不需要自己维护链表。
核心特性:
- 每个定时器对应一个
timer_t对象,独立管理 - 支持两种通知方式:发送信号(可以是实时信号)、启动线程执行回调
- 时间精度更高,支持纳秒级设置
简化代码示例:
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <time.h> typedef struct TimerArg { void (*callback)(void*); void* arg; } TimerArg; // 实时信号处理函数 static void timer_signal_handler(int sig, siginfo_t* si, void* uc) { TimerArg* arg = si->si_value.sival_ptr; arg->callback(arg->arg); free(arg); } // 创建一个单次触发的实时定时器 timer_t create_realtime_timer(long long ms, void (*cb)(void*), void* arg) { struct sigevent sev = {0}; sev.sigev_notify = SIGEV_SIGNAL; sev.sigev_signo = SIGRTMIN; // 使用实时信号,避免冲突 TimerArg* timer_arg = malloc(sizeof(TimerArg)); timer_arg->callback = cb; timer_arg->arg = arg; sev.sigev_value.sival_ptr = timer_arg; // 设置信号处理函数 struct sigaction sa = {0}; sa.sa_sigaction = timer_signal_handler; sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask); sigaction(SIGRTMIN, &sa, NULL); timer_t timerid; timer_create(CLOCK_REALTIME, &sev, &timerid); // 设置超时时间 struct itimerspec its = {0}; its.it_value.tv_sec = ms / 1000; its.it_value.tv_nsec = (ms % 1000) * 1000000; timer_settime(timerid, 0, &its, NULL); return timerid; } // 取消定时器 void cancel_timer(timer_t timerid) { timer_delete(timerid); } // 示例回调 void test_callback(void* arg) { printf("Task %d triggered!\n", *(int*)arg); free(arg); } int main() { int* arg1 = malloc(sizeof(int)); *arg1 = 1; timer_t t1 = create_realtime_timer(2000, test_callback, arg1); int* arg2 = malloc(sizeof(int)); *arg2 = 2; timer_t t2 = create_realtime_timer(1000, test_callback, arg2); // 等待任务触发 pause(); cancel_timer(t1); cancel_timer(t2); return 0; }
方案3:使用事件驱动库(libevent/libuv)
如果你的项目本身是事件驱动架构,直接用成熟的事件库的定时器功能最省心——这些库已经封装好了多定时器的实现,处理了所有细节(时间精度、线程安全、跨平台兼容等)。
以libuv为例的简化示例:
#include <stdio.h> #include <stdlib.h> #include <uv.h> void timer_callback(uv_timer_t* handle) { int* arg = handle->data; printf("Task %d triggered!\n", *arg); free(arg); uv_timer_stop(handle); uv_close((uv_handle_t*)handle, NULL); free(handle); } void add_uv_timer(long long ms, void (*cb)(uv_timer_t*), void* arg) { uv_timer_t* timer = malloc(sizeof(uv_timer_t)); uv_timer_init(uv_default_loop(), timer); timer->data = arg; uv_timer_start(timer, cb, ms, 0); // 单次触发,循环触发则把最后一个参数设为ms } int main() { int* arg1 = malloc(sizeof(int)); *arg1 = 1; add_uv_timer(2000, timer_callback, arg1); int* arg2 = malloc(sizeof(int)); *arg2 = 2; add_uv_timer(1000, timer_callback, arg2); uv_run(uv_default_loop(), UV_RUN_DEFAULT); return 0; }
方案选择建议:
- 若需要极致兼容性(支持老版本Android):选方案1
- 若不想自己造轮子,且目标系统支持POSIX定时器:选方案2
- 若项目已用事件驱动库:直接用方案3
内容的提问来源于stack exchange,提问作者CCJ
相关产品推荐
相关产品推荐

