赞
踩
在Linux下使用定时器时,大都会使用timerfd系列函数,本文记录一个使用时遇到的一个问题以及解决办法。
以下代码是timerfd的一个基本使用,
#include <sys/timerfd.h> #include <sys/time.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <unistd.h> #include <pthread.h> #include <signal.h> #include <sys/epoll.h> #define handle_error(msg) \ do { perror(msg); exit(EXIT_FAILURE); } while (0) bool fRunning = true; int gTimerfd = -1; int initTimer(void); // 初始化定时器 void startTimer(int timerfd); // 开启定时器 void stopTimer(int timerfd); // 停止定时器 void print_elapsed_time(void); // 打印逝去的时间 // 当收到ctrl+c时的信号处理函数 void sig_handler(int signum) { stopTimer(gTimerfd); close(gTimerfd); gTimerfd = -1; fRunning = false; } void *thr_func(void *arg) { int timerfd = *(int*)(arg); startTimer(timerfd); print_elapsed_time(); printf("timer started\n"); uint64_t exp = 0; while (fRunning) { int ret = read(timerfd, &exp, sizeof(uint64_t)); if (ret == sizeof(uint64_t)) { print_elapsed_time(); } } return NULL; } int main(void) { signal(SIGINT, sig_handler); gTimerfd = initTimer(); if (gTimerfd < 0) { return -1; } pthread_t tid; pthread_create(&tid, NULL, thr_func, &gTimerfd); pthread_join(tid, NULL); return 0; } int initTimer(void) { int timerfd = timerfd_create(CLOCK_MONOTONIC, 0); if (timerfd == -1) { printf("timerfd_create fail\n"); } return timerfd; } void startTimer(int timerfd) { struct itimerspec new_value = {}; new_value.it_value.tv_sec = 0; new_value.it_value.tv_nsec = 50000000; // 50ms new_value.it_interval.tv_sec = 0; new_value.it_interval.tv_nsec = 50000000; // 50ms if (timerfd_settime(timerfd, 0, &new_value, NULL) == -1) { printf("timerfd_settime fail\n"); } } void stopTimer(int timerfd) { if (timerfd > 0) { struct itimerspec new_value = {}; new_value.it_value.tv_sec = 0; new_value.it_value.tv_nsec = 0; new_value.it_interval.tv_sec = 0; new_value.it_interval.tv_nsec = 0; if (timerfd_settime(timerfd, 0, &new_value, NULL) == -1) { printf("timerfd_settime fail\n"); } } } void print_elapsed_time(void) { static struct timeval start = {}; static int first_call = 1; if (first_call == 1) { first_call = 0; if (gettimeofday(&start, NULL) == -1) { handle_error("gettimeofday"); } } struct timeval current = {}; if (gettimeofday(¤t, NULL) == -1) { handle_error("gettimeofday"); } static int old_secs = 0, old_usecs = 0; int secs = current.tv_sec - start.tv_sec; int usecs = current.tv_usec - start.tv_usec; if (usecs < 0) { --secs; usecs += 1000000; } usecs = (usecs + 500)/1000; // 四舍五入 if (secs != old_secs || usecs != old_usecs) { printf("%d.%03d\n", secs, usecs); old_secs = secs; old_usecs = usecs; } }
代码比较简单,使用阻塞的timerfd (参见函数initTimer()),每隔50ms打印一下程序运行后累计的时间,按ctrl+c去停止定时器,并把全局变量fRunning置为false,这样可以让线程自然结束(不需要调用pthread_cancel)
上面的程序编译并运行,
g++ timerfd_test.cpp -pthread
效果如下,
运行时没问题,按ctrl+c时,发现程序卡主了,无法结束,
原因是:信号处理函数sig_handler里停止定时器后,read()函数阻塞住了,导致线程无法结束,这样main函数里的pthread_join()无法返回,归根到底是因为创建timerfd时选择了阻塞类型
既然是因为使用阻塞式timerfd造成的,那么使用非阻塞式的timerfd不就行了,把initTimer()改为如下,在创建时传递非阻塞的标志,
int initTimer(void)
{
int timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
if (timerfd == -1)
{
printf("timerfd_create fail\n");
}
return timerfd;
}
重新运行并按ctrl+c,效果如下,
确实正常结束了,但是CPU的使用率就飙升了…,可以使用top或htop进行查看,如下,达到74.8%
阻塞式调用可以降低CPU的使用率,如果把代码改回阻塞式,查看运行时的CPU使用率如下,只有0.3%…
代码再改回阻塞式的timerfd,如何解决无法结束的问题呢?经过资料查询,发现可以使用epoll+timerfd+eventfd这种方式来解决。
思路如下:
改进后代码如下,
#include <sys/timerfd.h> #include <sys/time.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <unistd.h> #include <pthread.h> #include <signal.h> #include <sys/epoll.h> #include <sys/eventfd.h> #define handle_error(msg) \ do { perror(msg); exit(EXIT_FAILURE); } while (0) bool fRunning = true; int gTimerfd = -1; int gEventfd = -1; int initTimer(void); void startTimer(int timerfd); void stopTimer(int timerfd); void print_elapsed_time(void); void sig_handler(int signum) { stopTimer(gTimerfd); close(gTimerfd); gTimerfd = -1; uint64_t u = 100; write(gEventfd, &u, sizeof(uint64_t)); fRunning = false; } void *thr_func(void *arg) { int timerfd = *(int*)(arg); startTimer(timerfd); int epollfd = epoll_create1(EPOLL_CLOEXEC); // or epoll_create(1) if (epollfd == -1) { handle_error("epoll_create1"); } struct epoll_event evTimer; evTimer.events = EPOLLIN; evTimer.data.fd = timerfd; epoll_ctl(epollfd, EPOLL_CTL_ADD, timerfd, &evTimer); struct epoll_event evEvent; evEvent.events = EPOLLIN; evEvent.data.fd = gEventfd; epoll_ctl(epollfd, EPOLL_CTL_ADD, gEventfd, &evEvent); const int maxEvents = 2; // 2 events struct epoll_event events[maxEvents]; print_elapsed_time(); printf("timer started\n"); uint64_t exp = 0; int result = 0; while (fRunning) { int nfd = epoll_wait(epollfd, events, maxEvents, -1); if (nfd > 0) { for (int i = 0; i < nfd; ++i) { exp = 0; result = 0; if (events[i].data.fd == timerfd) { result = read(timerfd, &exp, sizeof(uint64_t)); if (result == sizeof(uint64_t)) { print_elapsed_time(); } } else if (events[i].data.fd == gEventfd) { result = read(gEventfd, &exp, sizeof(uint64_t)); if (result == sizeof(uint64_t)) { if (exp == 100) { fRunning = false; } } } } } } return NULL; } int main(void) { signal(SIGINT, sig_handler); gTimerfd = initTimer(); if (gTimerfd < 0) { return -1; } gEventfd = eventfd(0, 0); if (gEventfd < 0) { return -1; } pthread_t tid; pthread_create(&tid, NULL, thr_func, &gTimerfd); pthread_join(tid, NULL); return 0; } int initTimer(void) { //int timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); int timerfd = timerfd_create(CLOCK_MONOTONIC, 0); if (timerfd == -1) { printf("timerfd_create fail\n"); } return timerfd; } void startTimer(int timerfd) { struct itimerspec new_value = {}; new_value.it_value.tv_sec = 0; new_value.it_value.tv_nsec = 50000000; // 50ms new_value.it_interval.tv_sec = 0; new_value.it_interval.tv_nsec = 50000000; // 50ms if (timerfd_settime(timerfd, 0, &new_value, NULL) == -1) { printf("timerfd_settime fail\n"); } } void stopTimer(int timerfd) { if (timerfd > 0) { struct itimerspec new_value = {}; new_value.it_value.tv_sec = 0; new_value.it_value.tv_nsec = 0; new_value.it_interval.tv_sec = 0; new_value.it_interval.tv_nsec = 0; if (timerfd_settime(timerfd, 0, &new_value, NULL) == -1) { printf("timerfd_settime fail\n"); } } } void print_elapsed_time(void) { static struct timeval start = {}; static int first_call = 1; if (first_call == 1) { first_call = 0; if (gettimeofday(&start, NULL) == -1) { handle_error("gettimeofday"); } } struct timeval current = {}; if (gettimeofday(¤t, NULL) == -1) { handle_error("gettimeofday"); } static int old_secs = 0, old_usecs = 0; int secs = current.tv_sec - start.tv_sec; int usecs = current.tv_usec - start.tv_usec; if (usecs < 0) { --secs; usecs += 1000000; } usecs = (usecs + 500)/1000; // 四舍五入 if (secs != old_secs || usecs != old_usecs) { printf("%d.%03d\n", secs, usecs); old_secs = secs; old_usecs = usecs; } }
添加了一个全局变量gEventfd ,用来放置eventfd,当收到ctrl+c时,先停止定时器,然后通过eventfd发送100,这个会触发epoll返回,然后就通过gEventfd 读取到100,最后退出while循环,顺利结束。这样就解决了之前的问题。
本文讲述使用epoll+timerfd+eventfd的组合来解决一个程序退出问题,如果有不对的地方,欢迎指正!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。