在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);
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;
new_value.it_interval.tv_sec = 0;
new_value.it_interval.tv_nsec = 50000000;
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这种方式来解决。
思路如下:
- timerfd使用阻塞式的(也可以非阻塞,因为使用了epoll来监测,epoll是阻塞的)
- 使用epoll来监测timerfd
- 添加eventfd,也使用epoll来监测
- 当需要结束时,先停止定时器(会造成阻塞),然后通过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);
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;
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, 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;
new_value.it_interval.tv_sec = 0;
new_value.it_interval.tv_nsec = 50000000;
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的组合来解决一个程序退出问题,如果有不对的地方,欢迎指正!
|