血淚教訓(xùn):Linux 定時器踩坑指南,看完少走三年彎路
大家好,我是小康。
朋友們,今天要跟大家聊個讓無數(shù)程序員頭疼的話題——Linux定時器。別看這玩意兒平時不起眼,但真要用起來,坑多得你想哭??

一、寫在前面的話
你有沒有遇到過這樣的場景?
- 寫個網(wǎng)絡(luò)程序,需要定期發(fā)送心跳包
- 做個游戲服務(wù)器,要每秒更新玩家狀態(tài)
- 搞個監(jiān)控系統(tǒng),定時檢查服務(wù)是否正常
- 甚至只是想讓程序延時幾秒再執(zhí)行某個操作
如果你點頭了,那恭喜你——定時器絕對是你繞不開的技能點!
我記得剛開始寫Linux程序的時候,遇到需要定時執(zhí)行任務(wù)的場景,第一反應(yīng)就是Google一下"Linux定時器怎么用"。結(jié)果搜出來一堆alarm()、setitimer()、timerfd_create()...看得我一頭霧水。
到底該用哪個?它們有什么區(qū)別?為什么有這么多種定時器?
相信很多小伙伴都有過同樣的困惑。今天咱們就來徹底搞懂Linux定時器的前世今生,保證看完之后你也能成為定時器專家!
二、第一代:古老而經(jīng)典的alarm()
1. 最簡單的開始
話說回來,Linux最早的定時器就是alarm(),簡單到爆:
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void timeout_handler(int sig) {
printf("時間到!該起床搬磚了!\n");
}
int main() {
signal(SIGALRM, timeout_handler);
alarm(5); // 5秒后觸發(fā)
pause(); // 等待信號
return 0;
}看起來挺簡單的對吧? 但是兄弟,這里面的坑可不少:
- 只能精確到秒 - 你想要毫秒級定時?不好意思,做不到
- 全局只能有一個 - 你在一個地方調(diào)用了alarm(10),另一個地方又調(diào)用alarm(5),前面那個就被覆蓋了
- 容易被系統(tǒng)調(diào)用中斷 - sleep()、read()這些函數(shù)被SIGALRM打斷后會提前返回
2. 真實踩坑經(jīng)歷
我當(dāng)年就因為不知道alarm()是全局唯一的,在一個多模塊的項目里用了好幾個alarm(),結(jié)果定時器莫名其妙地不按預(yù)期工作。調(diào)試了好久才發(fā)現(xiàn)是被互相覆蓋了。
三、第二代:更靈活的setitimer()
1. 進步在哪里?
既然alarm()這么局限,Linux就推出了升級版——setitimer():
#include <sys/time.h>
#include <signal.h>
#include <stdio.h>
void timer_handler(int sig) {
staticint count = 0;
printf("第%d次定時觸發(fā)!\n", ++count);
}
int main() {
struct itimerval timer;
signal(SIGALRM, timer_handler);
// 設(shè)置定時器:1秒后開始,每0.5秒觸發(fā)一次
timer.it_value.tv_sec = 1; // 首次觸發(fā)時間
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 0; // 重復(fù)間隔
timer.it_interval.tv_usec = 500000; // 0.5秒 = 500000微秒
setitimer(ITIMER_REAL, &timer, NULL);
while(1) {
pause(); // 等待信號
}
return 0;
}這就厲害多了!
- 支持微秒級精度
- 可以設(shè)置周期性觸發(fā)
- 有三種定時器類型(REAL、VIRTUAL、PROF)
2. 但是...新的問題來了
雖然setitimer()比alarm()強大,但還是有些讓人頭疼的地方:
- 還是基于信號 - 信號處理的那些坑一個都沒少
- 每個進程還是只能有一個ITIMER_REAL - 多個定時器?也不支持
- 信號可能丟失 - 在信號處理函數(shù)執(zhí)行期間,新的信號可能被丟棄
四、第三代:專業(yè)級的POSIX定時器
1. 更加專業(yè)的選擇
在timerfd出現(xiàn)之前,還有一個重要的過渡產(chǎn)品——POSIX定時器(timer_create系列)。這玩意兒是POSIX標(biāo)準(zhǔn)定義的,比setitimer()更專業(yè),但又沒有timerfd()那么現(xiàn)代化。
#include <time.h>
#include <signal.h>
#include <stdio.h>
timer_t timerid;
int timer_count = 0;
void timer_handler(int sig, siginfo_t *si, void *uc) {
timer_t *tidp = si->si_value.sival_ptr;
printf("第%d次POSIX定時器觸發(fā)!timer_id: %p\n", ++timer_count, tidp);
}
int main() {
struct sigevent sev;
struct itimerspec its;
struct sigaction sa;
// 設(shè)置信號處理函數(shù)
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = timer_handler;
sigemptyset(&sa.sa_mask);
sigaction(SIGUSR1, &sa, NULL);
// 創(chuàng)建定時器
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIGUSR1;
sev.sigev_value.sival_ptr = &timerid;
if (timer_create(CLOCK_REALTIME, &sev, &timerid) == -1) {
perror("timer_create failed");
return-1;
}
// 設(shè)置定時器參數(shù):1秒后開始,每500ms觸發(fā)一次
its.it_value.tv_sec = 1;
its.it_value.tv_nsec = 0;
its.it_interval.tv_sec = 0;
its.it_interval.tv_nsec = 500000000; // 500ms
timer_settime(timerid, 0, &its, NULL);
printf("POSIX定時器啟動,按Ctrl+C退出\n");
while (1) {
pause();
}
timer_delete(timerid);
return 0;
}看起來是不是比setitimer()復(fù)雜多了? 但功能也更強大:
2. POSIX定時器的優(yōu)勢
- 支持多個定時器 - 終于可以創(chuàng)建多個了!每個都有獨立的timer_t標(biāo)識
- 納秒級精度 - 和timerfd一樣精確
- 靈活的通知方式 - 不僅可以發(fā)信號,還可以創(chuàng)建線程或者什么都不做
- 更好的信息傳遞 - 可以通過siginfo_t傳遞額外信息
3. 三種通知方式
POSIX定時器最酷的地方是支持三種通知方式:
(1) 信號通知(最常用)
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIGUSR1;(2) 線程通知(高級用法)
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_notify_function = thread_handler;
sev.sigev_notify_attributes = NULL;(3) 無通知(輪詢模式)
sev.sigev_notify = SIGEV_NONE;
// 然后用timer_gettime()主動查詢4. 我的使用心得
POSIX定時器我在一個服務(wù)器監(jiān)控項目中用過,需要同時監(jiān)控多個不同的指標(biāo),每個指標(biāo)的檢查頻率都不一樣。用setitimer()根本搞不定,但POSIX定時器就很合適:
timer_t cpu_timer, memory_timer, disk_timer, network_timer;
// CPU使用率:每秒檢查一次
create_posix_timer(&cpu_timer, SIGUSR1, 1000);
// 內(nèi)存使用率:每30秒檢查一次
create_posix_timer(&memory_timer, SIGUSR2, 300000);
// 磁盤IO:每分鐘檢查一次
create_posix_timer(&disk_timer, SIGRTMIN, 600000);
// 網(wǎng)絡(luò)連接:每分鐘檢查一次
create_posix_timer(&network_timer, SIGRTMIN+1, 600000);這樣每個監(jiān)控任務(wù)都有自己獨立的定時器,互不干擾,代碼邏輯也很清晰。
但是...POSIX定時器也有它的問題:
- 還是基于信號 - 信號處理的坑一個都沒少
- 代碼復(fù)雜 - 比alarm()和setitimer()復(fù)雜多了
- 移植性問題 - 有些老系統(tǒng)支持不夠好
所以雖然功能強大,但在現(xiàn)代Linux開發(fā)中,大家更傾向于直接用timerfd。
五、第四代:現(xiàn)代化的timerfd
1. 革命性的改變
到了Linux 2.6.25,終于迎來了真正的現(xiàn)代化定時器——timerfd!
這東西徹底改變了游戲規(guī)則:把定時器變成了文件描述符!
#include <sys/timerfd.h>
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
int main() {
int timer_fd;
struct itimerspec timer_spec;
uint64_t expirations;
// 創(chuàng)建定時器文件描述符
timer_fd = timerfd_create(CLOCK_REALTIME, 0);
if (timer_fd == -1) {
perror("timerfd_create failed");
return-1;
}
// 設(shè)置定時器:2秒后開始,每1秒觸發(fā)一次
timer_spec.it_value.tv_sec = 2;
timer_spec.it_value.tv_nsec = 0;
timer_spec.it_interval.tv_sec = 1;
timer_spec.it_interval.tv_nsec = 0;
timerfd_settime(timer_fd, 0, &timer_spec, NULL);
printf("定時器啟動,等待觸發(fā)...\n");
for (int i = 0; i < 5; i++) {
// 就像讀文件一樣讀取定時器
ssize_t bytes = read(timer_fd, &expirations, sizeof(expirations));
if (bytes == sizeof(expirations)) {
printf("定時器觸發(fā)了%llu次\n", expirations);
}
}
close(timer_fd);
return 0;
}這簡直是質(zhì)的飛躍!
2. 為什么timerfd這么香?
- 文件描述符 - 可以用select()、poll()、epoll()監(jiān)聽,完美融入事件循環(huán)
- 納秒級精度 - 想要多精確有多精確
- 無限個定時器 - 想創(chuàng)建多少個就創(chuàng)建多少個
- 不依賴信號 - 再也不用擔(dān)心信號處理的各種坑
- 更好的并發(fā)支持 - 在事件驅(qū)動的程序中表現(xiàn)出色
3. 配合epoll使用更香
#include <stdio.h>
#include <unistd.h>
#include <sys/timerfd.h>
#include <sys/epoll.h>
#include <stdint.h>
int main() {
int timerfd1, timerfd2, epollfd;
struct itimerspec its;
struct epoll_event ev, events[10];
uint64_texp;
// 創(chuàng)建兩個定時器
timerfd1 = timerfd_create(CLOCK_REALTIME, 0);
timerfd2 = timerfd_create(CLOCK_REALTIME, 0);
// 創(chuàng)建epoll實例
epollfd = epoll_create1(0);
// 將定時器加入epoll監(jiān)聽
ev.events = EPOLLIN;
ev.data.fd = timerfd1;
epoll_ctl(epollfd, EPOLL_CTL_ADD, timerfd1, &ev);
ev.data.fd = timerfd2;
epoll_ctl(epollfd, EPOLL_CTL_ADD, timerfd2, &ev);
// 設(shè)置定時器1:每1秒觸發(fā)
its.it_value.tv_sec = 1;
its.it_value.tv_nsec = 0;
its.it_interval.tv_sec = 1;
its.it_interval.tv_nsec = 0;
timerfd_settime(timerfd1, 0, &its, NULL);
// 設(shè)置定時器2:每2秒觸發(fā)
its.it_value.tv_sec = 2;
its.it_interval.tv_sec = 2;
timerfd_settime(timerfd2, 0, &its, NULL);
printf("高性能定時器系統(tǒng)啟動!\n");
while (1) {
int nfds = epoll_wait(epollfd, events, 10, -1);
for (int n = 0; n < nfds; n++) {
int fd = events[n].data.fd;
read(fd, &exp, sizeof(uint64_t));
if (fd == timerfd1) {
printf("? 快速定時器觸發(fā) (1秒間隔)\n");
} elseif (fd == timerfd2) {
printf("?? 慢速定時器觸發(fā) (2秒間隔)\n");
}
}
}
return 0;
}這就是現(xiàn)代Linux程序的標(biāo)準(zhǔn)寫法! 事件驅(qū)動,高性能,代碼還清晰易懂。
六、實際項目中該選哪個?
1. 快速決策指南
如果你只是想要個簡單的定時:
alarm(5); // 夠用了,別想太多如果需要周期性定時,而且精度要求不高:
setitimer(ITIMER_REAL, &timer, NULL); // 經(jīng)典選擇如果需要多個定時器,但不想用太新的API:
timer_create() + timer_settime(); // POSIX標(biāo)準(zhǔn),兼容性好如果是現(xiàn)代項目,特別是網(wǎng)絡(luò)服務(wù)器:
timerfd_create() + epoll(); // 這就對了!2. 性能對比
我之前做過一個簡單的功能測試,看看各種定時器的支持能力:
- alarm(): 全局只能有1個,新的會覆蓋舊的
- setitimer(): 每種類型只能1個(REAL、VIRTUAL、PROF),最多3個
- POSIX定時器: 支持多個,具體數(shù)量受系統(tǒng)限制(通常幾百個),但信號處理開銷較大
- timerfd(): 支持多個,數(shù)量主要受文件描述符限制
實際項目中的選擇建議:
- 如果只需要1-2個定時器:setitimer()夠用
- 如果需要多個定時器:POSIX定時器和timerfd都可以,但timerfd在事件驅(qū)動程序中更高效
- 如果是高并發(fā)網(wǎng)絡(luò)程序:timerfd() + epoll()性能最好,因為可以和其他I/O事件統(tǒng)一處理
3. 兼容性考慮
- alarm()/setitimer(): 幾乎所有Unix系統(tǒng)都支持
- POSIX定時器: 理論上是POSIX標(biāo)準(zhǔn),但實際支持情況復(fù)雜:Linux 2.6+原生支持(但可能需要鏈接 -lrt),macOS/BSD支持有限,Windows需要通過Cygwin等兼容層
- timerfd(): Linux 2.6.25+專有,其他系統(tǒng)不支持
實際上,跨平臺的定時器API是一個普遍難題,每個操作系統(tǒng)都有自己的實現(xiàn)方式。如果你的項目需要真正的跨平臺,可能需要:
- 使用第三方庫(如libuv、libevent)
- 或者針對不同平臺編寫不同的實現(xiàn)
七、進階技巧分享
1. 高精度定時器
想要更高的精度?試試CLOCK_MONOTONIC:
timer_fd = timerfd_create(CLOCK_MONOTONIC, 0);CLOCK_MONOTONIC不受系統(tǒng)時間調(diào)整影響,更適合做精確的間隔定時。
2. 一次性定時器
有時候你只想要一個一次性的延時:
timer_spec.it_value.tv_sec = 5; // 5秒后觸發(fā)
timer_spec.it_value.tv_nsec = 0;
timer_spec.it_interval.tv_sec = 0; // 不重復(fù)
timer_spec.it_interval.tv_nsec = 0;3. 定時器管理器
在復(fù)雜項目中,你可能需要管理很多定時器。我一般會封裝一個定時器管理器:
typedef struct {
int fd;
void (*callback)(void *data);
void *data;
} Timer;
// 創(chuàng)建定時器
Timer* create_timer(int interval_ms, void (*callback)(void*), void *data);
// 刪除定時器
void destroy_timer(Timer *timer);
// 在主事件循環(huán)中處理定時器事件
void handle_timer_event(Timer *timer);這樣管理起來就清爽多了。
八、總結(jié):定時器進化的啟示
從alarm()到timerfd(),Linux定時器的進化史其實反映了整個系統(tǒng)編程的發(fā)展趨勢:
- 從簡單到復(fù)雜 - 功能越來越強大
- 從單一到多元 - 支持更多使用場景
- 從同步到異步 - 更好地融入事件驅(qū)動架構(gòu)
- 從信號到文件描述符 - 統(tǒng)一的編程模型
如果你是新手,建議從alarm()開始理解基本概念,了解一下POSIX定時器的功能特性,然后直接跳到timerfd()學(xué)習(xí)現(xiàn)代用法。
如果你是老手,是時候把那些老舊的alarm()和setitimer()代碼重構(gòu)了。如果項目只在Linux上運行,直接用timerfd();如果需要跨平臺,考慮使用成熟的第三方庫。
選擇建議總結(jié):
- 學(xué)習(xí)路徑: alarm() → POSIX定時器概念 → timerfd()實踐
- 跨平臺項目: 使用libuv、libevent等成熟庫,別自己造輪子
- Linux專項目: 直接用timerfd() + epoll()
- 簡單腳本: alarm()夠用,別過度設(shè)計



























