Linux C语言定时任务的实现

1. 前情提要

最近需要编写一个小的测试程序,在while循环中读取数据并且不间断的读取10分钟,10分钟之后就不再读取数据。一开始是想用一个很大的数来计数,每次循环减一,减到0就退出while,但是这种方法不精准,而且这个数字也已经超出范围。后来就想定义一个flag,刚开始flag为true,while进入循环读取数据,等10分钟后flag被某个任务函数赋值为false,while循环就退出了。大致框架如下:

static bool flag = true;
void fun()
{
	flag = false;
}
int main()
{
	while(flag)
	{
		//读取数据代码
	}
}

所以这里需要一个定时任务,一到时间就调用fun函数设置flag。在此,我使用了POSIX的定时器来达到我的目的。

2. POSIX相关函数介绍

posix相关的定时器函数包含在以下头文件中:

#include <time.h>
#include <signal.h>

2.1 创建定时器

函数原型:

int timer_create(clockid_t clock_id, struct sigevent *evp, timer_t *timerid)

函数说明:创建一个POSIX标准的定时器
参数说明:
clock_id:系统时钟的宏,该参数表明了定时器是基于哪个系统时钟来创建的。常见的宏有以下:

#define CLOCK_REALTIME    0    
//表示从1970.1.1到目前系统时间,属于相对时间

#define CLOCK_MONOTONIC   1
//单调的时间,也是绝对的时间,表示系统开启到目前的时间

#define CLOCK_PROCESS_CPUTIME_ID  2
// 本进程到当前代码系统CPU花费的时间

#define CLOCK_THREAD_CPUTIME_ID  3
//本线程到当前代码系统CPU花费的时间

除了以上宏,还有七种系统时钟的宏,这里就不一一介绍了,在time.h中可以查看。

evp:环境值,其结构体主要成员以下有如下:

struct sigevent
{
	sigval_t sigev_value;
	int sigev_signo;
	int sigev_notify;
	.....
}
union sigval_t
{
	int sival_int;
	void* sival_ptr;
}

sigev_notify表示定时器到期后需要采取的行为,它的取值有如下几种:

enum
{
SIGEV_SIGNAL = 0, // 设置该值时说明定时器到期时会产生信号,该信号由sigev_signo指定
SIGEV_NONE , // 设置该值防止定时器到期时产生信号
SIGEV_THREAD, //通过线程创建传递信号,这个不太确定
SIGEV_THREAD_ID //表示信号会发送到指定的线程
}

sigev_signo表示定时器到期时将会发出的信号。这些信号在signum.h中有定义。常用的信号由如下:

#define SIGALRM 		14 // 时钟信号
#define SIGUSR1			10 //用户自定义信号1
#define SIGUSR2 		12  //用户自定义信号2
...

还有最后一个成员是sigev_value,它则是来绑定定时器的。表示这些设置的环境将会作用到哪个定时器上。

该函数最后一个参数是timerid,表示定时器的id,定时器创建成功,将会产生一个id,而该id就会被赋值给timerid。

函数返回值:
返回0表示成功,返回-1表示失败。

2.2 初始化定时器

经过上述函数创建了一个定时器之后,还需要设置定时器的定时周期以及启动时钟周期(即过了多久开始启动时钟)。这项工作交由timer_settime 接口来完成,其函数原型如下:

int timer_settime (timer_t timerid, int flags,
			       const struct itimerspec *value,
			       struct itimerspec *old_value)

函数参数说明:
timer_id:定时器的ID,指定初始化的定时器,由timer_create函数产生。
flags:0表示相对时间,1表示绝对时间。
value:保存一个结构体的地址,该结构体就包含了定时周期以及启动周期。
结构体如下:

struct itimerspec
  {
    struct timespec it_interval;  //该值表示定时器启动后定时周期是多少
    struct timespec it_value;  //该值表示过多久开始启动定时器
  };

而结构体timespec则有两个成员,分别是秒和纳秒,如下:

struct timespec
{
  __time_t tv_sec;		/* Seconds.  */
  __syscall_slong_t tv_nsec;	/* Nanoseconds.  */
};

可见该定时器的精准度还是非常高的。
参数old_value通常情况下都是取0值或者NULL。

2.3 删除定时器
任务完成后,不需要定时器则可以使用下面的接口来删除定时器。
函数原型:

int timer_delete (timer_t __timerid)

函数只有一个参数,即定时器的ID,表明删除指定id的定时器。

好了,现在定时器有了,并且也可以设置定时器到期时产生的信号,现在就差信号产生时,怎么去触发执行指定的任务了。这就需要signal函数介入了。

2.4 signal函数
函数原型:

void (*signal(int sig, void (*func)(int)))(int)

函数说明:该函数是用于设置捕获到某一信号时所要采用的动作。
参数说明:
signum:指明了所要处理的信号类型,它可以取除SIGKILL和SIGSTOP之外的任意信号。
第二个参数则是一个函数指针,该函数无返回值,且包含一个int型的参数,表明了当产生信号时,函数指针指向的函数将会被调用。

3. 简单的小例子

接下来看一个简单的小例子来了解一下定时器的使用。该程序的功能就是在while中隔1s打印一个字符串,10s后退出while结束打印。

#include<stdio.h>
#include<signal.h>
#include<time.h>
#include<string.h>
#include <unistd.h>

static bool flag = true;   
timer_t timeid;  //定义一个全局的定时器id
 
void task(int i)  
{  
    printf("task start\n");
    flag = false;
}  
  
 void create_timer()
 {
 /****创建定时器***********/
    struct sigevent evp;  //环境结构体
    int ret = 0;

    memset(&evp, 0, sizeof(struct sigevent));

    evp.sigev_value.sival_ptr = &timeid;    //绑定i定时器
    evp.sigev_notify = SIGEV_SIGNAL;  //设置定时器到期后触发的行为是为发送信号
    evp.sigev_signo = SIGUSR1;  //设置信号为用户自定义信号1
    signal(SIGUSR1, task);  //绑定产生信号后调用的函数
  
    ret = timer_create(CLOCK_REALTIME, &evp, &timeid);  //创建定时器
    if( ret  == 0)   
        printf("timer_create ok\n");  
 } 
  
void init_timer()
{
    int ret = 0;
    struct itimerspec ts;  
     ts.it_interval.tv_sec = 1;  //设置定时器的定时周期是1s
    ts.it_interval.tv_nsec = 0;  
    ts.it_value.tv_sec = 10;  //设置定时器10s后启动
    ts.it_value.tv_nsec = 0;  
  
    ret = timer_settime(timeid, 0, &ts, NULL);  //初始化定时器
    if( ret ==0)   
        printf("timer_settime ok\n");  
}
int main()  
{  
    create_timer();
    init_timer();
    while(flag)
    {
        printf("ss\n");
        usleep(1000*1000);
    } 
} 

注意:编译程序时需要加上-lrt。

测试结果:

timer_create ok
timer_settime ok
ss
ss
ss
ss
ss
ss
ss
ss
ss
ss
task start

这样一来,while循环运行的时间就可以随意控制了。

  • 7
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言实现一个简单的线程池可以按照以下步骤进行: 1. 定义线程池结构体:创建一个结构体,包含线程池中的线程数量、任务队列、互斥锁、条件变量等必要的成员。 ```c typedef struct { pthread_t *threads; // 线程数组 int thread_count; // 线程数量 task_queue_t *task_queue; // 任务队列 pthread_mutex_t mutex; // 互斥锁 pthread_cond_t cond; // 条件变量 int shutdown; // 线程池关闭标志 } thread_pool_t; ``` 2. 定义任务队列结构体:创建一个结构体,用于存储任务队列的信息,包括任务数组、任务数量、头尾指针等成员。 ```c typedef struct { task_t **tasks; // 任务数组 int task_count; // 任务数量 int head; // 队列头指针 int tail; // 队列尾指针 } task_queue_t; ``` 3. 定义任务结构体:创建一个结构体,用于存储具体的任务信息,例如任务函数指针和参数。 ```c typedef struct { void (*function)(void *); // 任务函数指针 void *argument; // 任务参数 } task_t; ``` 4. 初始化线程池:创建线程池,并初始化线程数组、任务队列、互斥锁、条件变量等。 5. 添加任务:将任务添加到任务队列中,并通过条件变量通知线程池中的线程有新任务可执行。 6. 线程函数:线程池中的线程函数,循环从任务队列中取出任务并执行。 7. 等待线程池任务完成:可以通过条件变量等待所有任务执行完毕。 8. 销毁线程池:释放线程池中的资源,包括线程数组、任务队列、互斥锁、条件变量等。 这是一个简单的线程池实现的框架,你可以根据自己的需求进行具体的功能扩展和优化。注意在并发环境中使用互斥锁和条件变量来保证线程安全。希望对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值