在服務端程序設計中,與時間有關的常見任務有:
- 獲取當前時間,計算時間間隔;
- 定時操作,比如在預定的時間執行一項任務,或者在一段延時之后執行一項任務。
Linux 時間函數
Linux 的計時函數,用於獲得當前時間:
- time(2) / time_t (秒)
- ftime(3) / struct timeb (毫秒)
- gettimeofday(2) / struct timeval (微秒)
- clock_gettime(2) / struct timespec (納秒)
- gmtime / localtime / timegm / mktime / strftime / struct tm (這些與當前時間無關)
定時函數,用於讓程序等待一段時間或安排計划任務:
- sleep
- alarm
- getitimer / setitimer
- timer_create / timer_settime / timer_gettime / timer_delete
- timerfd_create / timerfd_gettime / timerfd_settime
- 條件變量pthread_cond_timedwait實現
- IO多路復用select, epoll實現
一般情況下
獲取當前時間常用
gettimerofday,因為它的精度是1us,並且在x86平台上它是用戶態實現的,沒有系統調用和上下文切換的開銷。
定時函數中:
- sleep / alarm / usleep 在實現時有可能用了信號 SIGALRM,在多線程程序中處理信號是個相當麻煩的事情,應當盡量避免。(近期我會寫一篇博客仔細講講“多線程、RAII、fork() 與信號”)
- nanosleep 和 clock_nanosleep 是線程安全的,但是在非阻塞網絡編程中,絕對不能用讓線程掛起的方式來等待一段時間,程序會失去響應。正確的做法是注冊一個時間回調函數。
- getitimer 和 timer_create 也是用信號來 deliver 超時,在多線程程序中也會有麻煩。timer_create 可以指定信號的接收方是進程還是線程,算是一個進步,不過在信號處理函數(signal handler)能做的事情實在很受限。
- timerfd_create 把時間變成了一個文件描述符,該“文件”在定時器超時的那一刻變得可讀,這樣就能很方便地融入到 select/poll 框架中,用統一的方式來處理 IO 事件和超時事件。l
- 利用select, epoll的timeout實現定時功能,它們的缺點是定時精度只有毫秒,遠低於 timerfd_settime 的定時精度。
實現
下面是用timer_create實現的一個定時器:
#include <stdio.h> #include <time.h> #include <stdlib.h> #include <signal.h> #include <string.h> #include <unistd.h> void sig_handler(int signo) { switch(signo) { case SIGUSR1: printf("receive sigusr1! \n"); break; case SIGALRM: printf("receive sigarlm!\n"); break; } } int main() { /** struct sigaction { void (*sa_handler)(int);信號響應函數地址 void (*sa_sigaction)(int, siginfo_t *, void *); 但sa_flags為SA——SIGINFO時才使用 sigset_t sa_mask; 說明一個信號集在調用捕捉函數之前,會加入進程的屏蔽中,當捕捉函數返回時,還原 int sa_flags; void (*sa_restorer)(void);未用 }; */ timer_t timer1, timer2; struct sigevent evp1, evp2; struct sigaction act; //for timer1 memset(&act, 0, sizeof(act)); act.sa_handler = sig_handler; act.sa_flags = 0; sigemptyset(&act.sa_mask); if (sigaction(SIGUSR1, &act, NULL) == -1) { perror("fail to sigaction"); exit(-1); } //for timer2 memset(&act, 0, sizeof(act)); act.sa_handler = sig_handler; act.sa_flags = 0; sigemptyset(&act.sa_mask); if (sigaction(SIGALRM, &act, NULL) == -1) { perror("fail to sigaction"); exit(-1); } //for timer1 memset(&evp1, 0, sizeof(struct sigevent)); evp1.sigev_signo = SIGUSR1; evp1.sigev_notify = SIGEV_SIGNAL; if (timer_create(CLOCK_REALTIME, &evp1, &timer1) == -1) { perror("fail to timer_create"); exit(-1); } struct itimerspec it; it.it_interval.tv_sec = 2; it.it_interval.tv_nsec = 0; it.it_value.tv_sec = 1; it.it_value.tv_nsec = 0; if (timer_settime(timer1, 0, &it, 0) == -1) { perror("fail to timer_settime"); exit(-1); } //for timer2 memset(&evp2, 0, sizeof(struct sigevent)); evp2.sigev_signo = SIGALRM; evp2.sigev_notify = SIGEV_SIGNAL; if (timer_create(CLOCK_REALTIME, &evp2, &timer2) == -1) { perror("fail to timer_create"); exit(-1); } it.it_interval.tv_sec = 4; it.it_interval.tv_nsec = 0; it.it_value.tv_sec = 2; it.it_value.tv_nsec = 0; if (timer_settime(timer2, 0, &it, 0) == -1) { perror("fail to timer_settime"); exit(-1); } for(;;); return 0; }
以及一個用setitimer實現的定時器
setitimer中的第一個參數有三類:
這里說得很清楚。
ITIMER_REAL decrements in real time, and delivers SIGALRM upon expiration. ITIMER_VIRTUAL decrements only when the process is executing, and delivers SIGVTALRM upon expiration. ITIMER_PROF decrements both when the process executes and when the system is executing on behalf of the process. Coupled with ITIMER_VIRTUAL, this timer is usually used to profile the time spent by the application in user and kernel space. SIGPROF is delivered upon expiration.
#include <signal.h> #include <stdio.h> #include <string.h> #include <sys/time.h> void timer_handler (int signum) { switch(signum) { case SIGALRM: printf("sigarlm !\n"); break; case SIGVTALRM: printf("sigvtalrm !\n"); break; } } int main () { struct sigaction sa; struct itimerval timer; memset (&sa, 0, sizeof (sa)); sa.sa_handler = &timer_handler; sigaction (SIGVTALRM, &sa, NULL); sa.sa_handler = &timer_handler; sigaction(SIGALRM, &sa, NULL); timer.it_value.tv_sec = 0; timer.it_value.tv_usec = 250000; timer.it_interval.tv_sec = 0; timer.it_interval.tv_usec = 250000; setitimer (ITIMER_REAL, &timer, NULL); timer.it_value.tv_sec = 1; timer.it_value.tv_usec = 0; timer.it_interval.tv_sec = 1; timer.it_interval.tv_usec = 0; setitimer (ITIMER_VIRTUAL, &timer, NULL); while (1); }
參考:http://www.cnblogs.com/Solstice/archive/2011/02/06/1949555.html