我们在写程序时,很多时候希望程序能够按照固定的周期执行。比较笨的办法是用一个while(1)循环,在循环里用一个sleep或者usleep函数延时,延时到一定时间执行需要执行的代码。这种方法忽略了程序代码运行的时间,所以程序循环的时间就不准了。为了能够活动比较准的定时时间,可以使用timer模块。

        timer模块的使用方法有2种比较常用的的用法,一种是产生新线程的方式,另一种是产生信号的方式。

1、timer产生新线程

       这种使用方法,当timer定时结束后会产生一个新的线程,在新的线程中执行用户代码,执行完后退出线程。timer的初始化代码如下所示。

timerInit(int sec, int usec)
{
    timer_t timerid;
    struct sigevent evp;
    memset(&evp, 0, sizeof(struct sigevent)); //清零初始化
    evp.sigev_value.sival_int = 111;          //标识定时器的
    evp.sigev_notify = SIGEV_THREAD;          //线程通知的方式,派驻新线程
    evp.sigev_notify_function = timerHandle;  //线程函数地址

    if (timer_create(CLOCK_REALTIME, &evp, &timerid) == -1)
    {
        perror("fail to timer_create");
        exit(-1);
    }

    struct itimerspec it;
    it.it_interval.tv_sec = sec;        //间隔s
    it.it_interval.tv_nsec = usec*1000; //间隔ns
    it.it_value.tv_sec = sec;
    it.it_value.tv_nsec = usec*1000;
    if (timer_settime(timerid, 0, &it, NULL) == -1)
    {
        perror("fail to timer_settime");
        exit(-1);
    }
}

timer的定时时间通过两个形参sec和usec传递,分别代表秒和微妙。程序中在主函数中调用timerInit函数时设置的定时周期为1秒。evp.sigev_notify = SIGEV_THREAD;将timer设置为派驻新线程的方式,evp.sigev_notify_function = timerHandle;这一句指定新线程的函数地址为timerHandle。也就是说,当1秒钟定时周期结束时,程序启动一个新线程,新线程转到timerHandle函数执行,在timerHandle函数中,执行的是代码命令,如下所示。

void timerHandle(union sigval v)
{
	printf("Hello!\n");
}

采用下面的命令对程序进行编译

gcc timer_test.c -o timer_test -lrt

注意一定要加上 -lrt,否则编译不能通过。执行程序,则每隔一秒打印一次Hello!

2、timer产生信号

        timer产生新线程的方式能够准确的产生定时周期,但也存在问题。如果周期执行的代码的执行时间超过定时周期,则会导致一个新起的线程没有执行完,又新起了一个新的线程。如果两个线程访问了相同的资源,比如一个很长的数组,就会导致冲突,可能会导致程序异常退出。

        当然,如果每个周期代码执行过程都超时,那就要考虑优化代码或者修改定时周期的办法了。但如果只是某个周期超时,其他周期不超时,就不太好解决了。某个周期的超时就可能导致程序崩溃。

        timer产生信号的方式可以有效解决上述问题,与产生新线程不同,产生信号的方式,不会产生新线程。当某个周期代码执行超时时,另一个计时信号产生时,信号会被阻塞,直到上个周期的代码执行完之后,再执行新的代码。

        这种方式,timer初始化过程如下所示。

timerInit(int sec, int usec)
{
	timer_t timerid;
	struct sigevent evp;
	struct sigaction act;	

	memset(&act, 0, sizeof(act));
	act.sa_handler = timerHandle;
	act.sa_flags = 0;
	
	sigemptyset(&act.sa_mask);
     
        sigaction(SIGUSR1, &act, NULL);

	memset(&evp, 0, sizeof(struct sigevent)); //清零初始化
	evp.sigev_value.sival_int = 111;          //标识定时器的
        evp.sigev_signo = SIGUSR1;
        evp.sigev_notify = SIGEV_SIGNAL;          //产生信号

	if (timer_create(CLOCK_REALTIME, &evp, &timerid) == -1)
	{
		perror("fail to timer_create");
		exit(-1);
	}

	struct itimerspec it;
	it.it_interval.tv_sec = sec;        //间隔s
	it.it_interval.tv_nsec = usec*1000; //间隔ns
	it.it_value.tv_sec = sec;
	it.it_value.tv_nsec = usec*1000;
	if (timer_settime(timerid, 0, &it, NULL) == -1)
	{
		perror("fail to timer_settime");
		exit(-1);
	}
}

        代码中,evp.sigev_notify = SIGEV_SIGNAL;将timer的工作方式设置为产生信号的方式,evp.sigev_signo = SIGUSR1;设置产生的信号为SIGUSR1。act.sa_handler = timerHandle;这一句将信号的处理函数设置为timerHandle,sigaction(SIGUSR1, &act, NULL);将SIGUSR1信号与timerHandle函数绑定在一起。当定时周期结束时,产生信号并触发timerHandle函数执行。如果上一个周期的代码没有执行完,则等待。

        同样的方法编译代码,并执行。与上一个实例现象一样。

本文中的源码可以从本文的资源中下载。

 

Logo

更多推荐