Linux应用需求:无需处理SIGALRM的5秒看门狗式定时器方案选型
推荐几种无需信号处理的Linux内部看门狗定时器实现方案
嘿,你的需求其实很典型——实现一个应用内的"软看门狗",5秒没重置就触发动作,还不想碰SIGALRM信号对吧?我给你梳理几个靠谱的方案,都是完全避开信号处理的:
方案1:独立看门狗线程 + 时间戳检查
这个方案最直观,完全不用依赖系统定时器的信号机制,逻辑简单易懂:
- 启动一个单独的pthread线程,专门负责看门狗逻辑
- 主线程和看门狗线程共享一个带互斥锁保护的时间戳变量(或者原子变量,比如
std::atomic<time_t>) - 每次需要"踢"定时器的时候,主线程就更新这个时间戳为当前时间
- 看门狗线程循环执行:
- 加锁读取时间戳,计算当前时间和上次更新时间的差值
- 如果差值≥5秒,立即触发你的指定动作
- 如果没到,解锁后sleep一小段时间(比如100ms)再重复检查
示例代码片段:
#include <pthread.h> #include <time.h> #include <unistd.h> #include <stdio.h> pthread_mutex_t timer_mutex = PTHREAD_MUTEX_INITIALIZER; time_t last_kick_time; int watchdog_running = 1; void* watchdog_thread(void* arg) { while (watchdog_running) { pthread_mutex_lock(&timer_mutex); time_t now = time(NULL); if (difftime(now, last_kick_time) >= 5) { printf("看门狗触发!执行指定动作...\n"); // 这里放你的触发逻辑 // 可以选择触发后继续运行,或者退出线程 } pthread_mutex_unlock(&timer_mutex); usleep(100000); // 每隔100ms检查一次 } return NULL; } // 踢定时器的函数 void kick_watchdog() { pthread_mutex_lock(&timer_mutex); last_kick_time = time(NULL); pthread_mutex_unlock(&timer_mutex); } int main() { last_kick_time = time(NULL); pthread_t tid; pthread_create(&tid, NULL, watchdog_thread, NULL); // 模拟业务逻辑,每隔3秒踢一次定时器 for (int i = 0; i < 10; i++) { sleep(3); kick_watchdog(); printf("踢了定时器\n"); } // 停止看门狗线程 pthread_mutex_lock(&timer_mutex); watchdog_running = 0; pthread_mutex_unlock(&timer_mutex); pthread_join(tid, NULL); return 0; }
这个方案的好处是逻辑透明,调试起来方便,适合小型应用或者对性能要求不高的场景。
方案2:timerfd_create + 事件循环(推荐给事件驱动型应用)
Linux提供了timerfd_create这个系统调用,它把定时器封装成一个文件描述符,可以用epoll/poll/select来监听,完全不需要处理信号,非常适合本来就用事件循环的应用(比如服务器程序):
- 用
timerfd_create创建一个定时器文件描述符 - 用
timerfd_settime设置初始5秒的超时时间 - 将这个fd加入到你的epoll实例中,监听可读事件
- 当定时器超时,fd会变成可读状态,读取fd里的超时计数就可以触发动作
- 需要"踢"定时器的时候,再次调用
timerfd_settime重新设置5秒超时即可
示例代码片段:
#include <sys/timerfd.h> #include <sys/epoll.h> #include <unistd.h> #include <stdio.h> #include <stdint.h> #define EPOLL_EVENTS_COUNT 10 int main() { int timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); if (timer_fd == -1) { perror("timerfd_create failed"); return 1; } // 设置初始5秒超时 struct itimerspec ts = { .it_interval = {0, 0}, // 只触发一次 .it_value = {5, 0} // 初始超时5秒 }; if (timerfd_settime(timer_fd, 0, &ts, NULL) == -1) { perror("timerfd_settime failed"); close(timer_fd); return 1; } int epoll_fd = epoll_create1(0); if (epoll_fd == -1) { perror("epoll_create1 failed"); close(timer_fd); return 1; } struct epoll_event event; event.events = EPOLLIN; event.data.fd = timer_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, timer_fd, &event) == -1) { perror("epoll_ctl failed"); close(timer_fd); close(epoll_fd); return 1; } struct epoll_events events[EPOLL_EVENTS_COUNT]; int running = 1; while (running) { int num_events = epoll_wait(epoll_fd, events, EPOLL_EVENTS_COUNT, -1); if (num_events == -1) { perror("epoll_wait failed"); break; } for (int i = 0; i < num_events; i++) { if (events[i].data.fd == timer_fd) { uint64_t expirations; ssize_t ret = read(timer_fd, &expirations, sizeof(expirations)); if (ret == sizeof(expirations)) { printf("定时器超时!触发动作...\n"); // 这里放你的触发逻辑 } } } // 模拟每隔3秒踢一次定时器 static int count = 0; if (count++ < 10) { sleep(3); // 重置定时器为5秒 if (timerfd_settime(timer_fd, 0, &ts, NULL) == -1) { perror("timerfd_settime reset failed"); running = 0; } printf("踢了定时器\n"); } else { running = 0; } } close(timer_fd); close(epoll_fd); return 0; }
这个方案的优势是高效,没有线程上下文切换的开销,完美融入事件驱动模型,适合高性能的服务端应用。
补充:关于timer_create的正确用法
你提到了timer_create但没找到重置方法,其实它是可以实现的,而且可以避开信号处理:
- 创建定时器时,把
sigevent的sigev_notify设为SIGEV_THREAD,这样超时的时候系统会自动创建一个新线程来执行你指定的回调函数,完全不用处理信号 - 重置定时器只需要调用
timer_settime重新设置超时时间即可
不过这个方案相对前两个来说,线程管理的复杂度稍高一点,所以优先级不如前两个,但也是一个可行选项。
总结
- 如果你的应用是简单的单线程/多线程程序,优先选方案1,逻辑简单易维护
- 如果你的应用本来就用epoll等事件循环,强烈推荐方案2,性能更好,更贴合事件驱动模型
- 两个方案都完全不需要处理SIGALRM信号,满足你的核心需求
内容的提问来源于stack exchange,提问作者Kreuzade
相关产品推荐
相关产品推荐

