C语言实现定时器demo,支持windows和Linux跨平台使用;
windows使用子线程pthread_cond_timedwait条件变量实现;
linux使用timerfd_create配合epoll_wait实现,timerfd是Linux为用户程序提供的一个定时器接口。这个接口基于文件描述符,通过文件描述符的可读事件进行超时通知,所以能够被用于select/poll的应用场景。
CSTimer.h
#ifndef SRC_UTIL_SRC_CTIMER_H_
#define SRC_UTIL_SRC_CTIMER_H_
#include <pthread.h>
#include <stdint.h>
#ifdef _WIN32
#define Using_Phtread 1
#else
#define Using_Phtread 0
#endif
typedef struct CSTimer{
int32_t taskId;
int32_t timerfd;
int32_t efd;
int32_t isStart;
int32_t isloop;
int32_t waitState;
int32_t waitTime;
pthread_t threadId;
#if Using_Phtread
pthread_mutex_t t_lock;
pthread_cond_t t_cond_mess;
#endif
void (*doTask)(int32_t taskId,void* user);
void* user;
}CSTimer;
#ifdef __cplusplus
extern "C"{
#endif
void create_timer(CSTimer* timer,void* user,int32_t taskId,int32_t waitTime);
void destroy_timer(CSTimer* timer);
void timer_start(CSTimer* timer);
void timer_stop(CSTimer* timer);
#ifdef __cplusplus
}
#endif
#endif
CSTimer.c
#include "CSTimer.h"
#include <stdio.h>
#include <string.h>
#if !Using_Phtread
#include <sys/time.h>
#include <sys/timerfd.h>
#include <sys/epoll.h>
#endif
#include <time.h>
#include <fcntl.h>
void create_timer(CSTimer *timer, void *user, int32_t taskId,int32_t waitTime)
{
if (timer == NULL)
return;
timer->isloop = 0;
timer->isStart = 0;
timer->waitState = 0;
timer->waitTime = waitTime;
#if Using_Phtread
pthread_mutex_init(&timer->t_lock,NULL);
pthread_cond_init(&timer->t_cond_mess,NULL);
#else
/** timerfd_create 创建一个新的计时器对象,并返回引用该计时器的文件描述符 * clockid 参数指定使用那种类型的时钟(clock)来实现计时器(timer),CLOCK_REALTIME:系统实时时间,CLOCK_MONOTONIC:从系统启动这一刻起开始计时,不受系统时间被用户改变的影响等 * flags:TFD_NONBLOCK:非阻塞模式,TFD_CLOEXEC:表示当程序执行exec函数时本fd将被系统自动关闭,表示不传递 */
timer->timerfd = timerfd_create(CLOCK_REALTIME, 0);
#endif
timer->efd = -1;
timer->user = user;
timer->doTask = NULL;
timer->taskId = taskId;
}
void destroy_timer(CSTimer *timer) {
if (timer == NULL)
return;
}
void* run_timer_thread(void *obj) {
CSTimer *timer = (CSTimer*) obj;
timer->isStart = 1;
timer->isloop = 1;
/*****************windows**********************/
#if Using_Phtread
struct timespec outtime;
struct timeval now;
pthread_mutex_lock(&timer->t_lock);
while (timer->isloop) {
gettimeofday(&now, NULL);
long nsec = now.tv_usec * 1000 + (timer->waitTime % 1000) * 1000000;
outtime.tv_sec=now.tv_sec + nsec / 1000000000 + timer->waitTime / 1000;
outtime.tv_nsec=nsec % 1000000000;
timer->waitState=1;
pthread_cond_timedwait(&timer->t_cond_mess, &timer->t_lock,&outtime);
timer->waitState=0;
if(timer->doTask) timer->doTask(timer->taskId, timer->user);
}
pthread_mutex_unlock(&timer->t_lock);
#else
/*****************linux**********************/
/** struct itimerspec 指定计时器的初始到期时间和到期间隔,包含两个 timespec结构体(一个单位秒,一个单位纳秒) * 参数it_interval,指定初始到期后重复计时器到期的时间段 * 参数it_value,指定计时器的初始到期时间 */
struct itimerspec itimer;
itimer.it_value.tv_sec = timer->waitTime / 1000;
itimer.it_value.tv_nsec = (timer->waitTime % 1000) * 1000 * 1000;
itimer.it_interval.tv_sec = timer->waitTime / 1000;
itimer.it_interval.tv_nsec = (timer->waitTime % 1000) * 1000 * 1000;
/** timerfd_settime 用于设置新的超时时间,并开始计时 * 参数ufd,是timerfd_create返回的文件句柄 * 参数flags,为1代表设置的是绝对时间;为0代表相对时间 * 参数utmr,为需要设置的时间 * 参数otmr,为定时器这次设置之前的超时时间 * 返回值,0成功,其他失败 */
int ret = timerfd_settime(timer->timerfd, /*TFD_TIMER_ABSTIME*/0, &itimer, NULL);
if (ret == -1) {
printf("err:timerfd_settime\n");
}
int opts;
opts = fcntl(timer->timerfd, F_GETFL);//获取文件打开方式的标志,标志值含义与open调用一致
if (opts < 0) {
printf("err:fcntl(sock,GETFL)\n");
_exit(1);
}
opts = opts | O_NONBLOCK;
//F_SETFL,设置文件状态标识,第三个参数0为阻塞,O_NONBLOCK为非阻塞,返回-1表示失败
if (fcntl(timer->timerfd, F_SETFL, opts) < 0) {
printf("err:fcntl(sock,SETFL,opts)\n");
_exit(1);
}
/** epoll_create 创建一个epoll实例(创建一个监测一个描述符epoll的句柄描述符) * 参数max_size,标识这个监听的数目最大有多大 * 返回值,新epoll实例的文件描述符,失败返回-1 */
timer->efd = epoll_create(1);
struct epoll_event tev;
tev.events = EPOLLIN | EPOLLET;//设置描述符的事件
tev.data.fd = timer->timerfd;//设置要监测的文件描述符的值
/** epoll_ctl 操作epoll函数所生成的实例(此处,把定时器描述符添加到epoll检测队列中,函数注册要监听的事件类型为ev中的events) * 参数epfd,操作的epoll实例的文件描述符 * 参数op,EPOLL_CTL_ADD:在文件描述符epfd所引用的epoll实例上注册目标文件描述符fd,并将事件事件与内部文件链接到fd * 参数fd,需要操作监听的文件描述符 * 参数epoll_event,结构体,需要监听的事件 */
epoll_ctl(timer->efd, EPOLL_CTL_ADD, timer->timerfd, &tev);
struct epoll_event ev[1];
while (timer->isloop) {
/** epoll_wait 等待事件的产生,即等待epoll文件描述符上的I / O事件 * 参数epfd,操作的epoll实例的文件描述符 * 参数events,用来从内核得到事件的集合 * 参数maxevents,告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size * 参数timeout,是超时时间(毫秒,0会立即返回,-1将会无限阻塞)。 * 返回值,返回需要处理的事件数目,如返回0表示已超时 */
int nev = epoll_wait(timer->efd, ev, 1, 200);//等待事件的产生,并把检测到的信息保存到ev这个结构体中
if (nev > 0 && (ev[0].events & EPOLLIN)) //检测到文件符可以读的事件
{
if(ev[0].data.fd == timer->timerfd)//检测到的文件描述符的值与被检测的文件描述符的值相等
{
uint64_t res;
int bytes = read(timer->timerfd, &res, sizeof(res));
if (timer->doTask)
timer->doTask(timer->taskId, timer->user);
}
}
}
#endif
timer->isStart = 0;
return NULL;
}
void timer_start(CSTimer *timer) {
if (timer == NULL)
return;
if (pthread_create(&timer->threadId, 0, run_timer_thread, timer)) {
printf("Thread::start could not start thread\n");
}
}
void timer_stop(CSTimer *timer) {
if (timer == NULL)
return;
if (timer->isStart) {
timer->isloop = 0;
#if Using_Phtread
if(timer->waitState){
pthread_mutex_lock(&timer->t_lock);
pthread_cond_signal(&timer->t_cond_mess);
pthread_mutex_unlock(&timer->t_lock);
}
#else
if (timer->isStart) {
struct epoll_event tev;
tev.events = EPOLLIN | EPOLLET;
tev.data.fd = timer->timerfd;
epoll_ctl(timer->efd, EPOLL_CTL_DEL, timer->timerfd, &tev);//把定时器文件描述从epoll监听列表中删除
close(timer->efd);
timer->efd = -1;
}
close(timer->timerfd);
timer->timerfd = -1;
#endif
while (timer->isStart)
usleep(1000);
}
}
使用QT测试:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>
#include <QDebug>
#include <QDateTime>
void g_doTask(int32_t taskId, void *user)
{
if (user == NULL)
return;
MainWindow *mw = (MainWindow*) user;
mw->doWork();
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
create_timer(&mCSTimer,this,1,5000);
mCSTimer.doTask = g_doTask;
timer_start(&mCSTimer);
qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<"tart"<<QDateTime::currentDateTime().toString("hh:mm:ss");
}
MainWindow::~MainWindow()
{
timer_stop(&mCSTimer);
destroy_timer(&mCSTimer);
delete ui;
}
void MainWindow::doWork()
{
qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<QDateTime::currentDateTime().toString("hh:mm:ss");
}
打印输出
[ ../mainwindow.cpp ] 25 MainWindow tart "17:22:53"
[ ../mainwindow.cpp ] 38 doWork "17:22:58"
[ ../mainwindow.cpp ] 38 doWork "17:23:03"
[ ../mainwindow.cpp ] 38 doWork "17:23:08"
[ ../mainwindow.cpp ] 38 doWork "17:23:13"
[ ../mainwindow.cpp ] 38 doWork "17:23:18"