在服務端程序設計中,與時間有關的常見任務有:
- 獲取當前時間,計算時間間隔;
- 定時操作,比如在預定的時間執行一項任務,或者在一段延時之后執行一項任務。
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
