posix timer使用入门

news/2024/11/3 2:30:59/

在c、c++开发中,如果使用定时器,我们经常会使用posix timer。posix timer使用较为灵活,本文介绍posix timer的使用。

1example

如下是使用posix timer的一个例子。主要使用了3个api:timer_create用于创建一个timer,但是timer创建之后,并没有启动;使用timer_settime可以启动定时器;使用timer_delete可以删除定时器。

使用posix timer编译的时候要带-lrt。

struct itimerspec {
    struct timespec it_value;    // 定时器启动之后,第一次执行的时间
    struct timespec it_interval; // 如果设置为0,那么定时器只执行一次;大于0,则是定时器的周期
};

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>void timer_cb(union sigval sigev_value) { printf("timer callback\n"); }int main() {struct sigevent sev;struct itimerspec its;timer_t timer_id;sev.sigev_notify = SIGEV_THREAD;sev.sigev_notify_function = timer_cb;sev.sigev_notify_attributes = NULL;timer_create(CLOCK_BOOTTIME, &sev, &timer_id);its.it_interval.tv_sec = 1;its.it_interval.tv_nsec = 0;its.it_value.tv_sec = 1;its.it_value.tv_nsec = 0;timer_settime(timer_id, 0, &its, NULL);sleep(10);timer_delete(timer_id);return 0;
}

2时钟源

timer_create的第一个参数是clockid,表示时钟的类型。

int timer_create(clockid_t clockid, struct sigevent *sevp,
                        timer_t *timerid);

linux中的时钟id比较多,在定时器中经常使用CLOCK_BOOTTIME或CLOCK_MONOTONIC。

CLOCK_REALTIME

从1970年1月1日8点开始的时间,这个就是系统时间,也叫墙上时间,比如2024年10月10日10时10分10秒。

如果系统的时间会变化,比如系统开启了NTP网络时钟同步,这样的话系统时间就会不断变化调整。

这种时钟不适合用在定时器中,因为时钟变化会影响时钟。适用于获取当前时间。

CLOCK_BOOTTIME系统启动之后开始计时,适合用在定时器中。
CLOCK_MONOTONIC系统启动之后开始计时,与CLOCK_BOOTTIME的区别是,如果系统休眠了,那么CLOCK_BOOTTIME也会继续计时;而CLOCK_MONOTONIC不会。
CLOCK_PROCESS_CPUTIME_ID用于计量进程实际消耗的cpu时间。
CLOCK_THREAD_CPUTIE_ID用于计量线程实际消耗的cpu时间。

3定时器触发方式

触发方式指的是定时器超时之后,以什么样的方式来触发调用定时器回调函数。posix timer中提供了如下 4 种方式。

信号通知方式

说明

SIGEV_NONE

超时之后什么都不做,需要用户调用函数timer_gettime来确定定时器是不是超时。

SIGEV_SIGNAL

当定时器超时,向进程发信号。

信号对整个进程都是有效的。也就是说,对于多线程程序来说,如果线程中有 sleep 或者其它阻塞的话,那么信号同样也会将这些阻塞唤醒,所以就需要对信号有额外的控制,控制起来比较复杂。

SIGEV_THREAD

posix内部会创建一个线程用来执行用户注册的定时器回调函数

SIGEV_THREAD_ID

需要用户自己创建一个线程,该线程会调用定时器回调函数

在工作中,经常使用SIGEV_THREAD和SIGEV_THREAD_ID。SIGEV_THREAD,每个定时器超时需要执行的时候都会创建一个线程,执行完毕,线程销毁,线程有底层库实现,不需要用户关心,使用方便,但是每次都要创建并销毁线程,对性能不友好;SIGEV_THREAD_ID中使用的线程需要由用户自己创建,使用稍复杂,但是用户可以进行更精确的控制。SIGEV_THREAD_ID在实际使用中,往往一个线程要使用一个线程 。

SIGEV_THREAD和SIGEV_THREAD_ID,针对每一个定时器都需要创建一个线程。如果我们想要使用多个定时器共用一个线程呢,这个时候,我们可以使用SIGEV_NONE,这种方式,我们可以使用一个线程,每隔一定时间(假设为T)检查所有定时器是不是超时,超时则执行回调函数,这里的T就是定时器的精度。

3.1SIGEV_NONE

如下是一个简单的例子,使用一个线程管理3个定时器。

(1)使用its.it_value.tv_sec == 0 && its.it_value.tv_nsec == 0来判断定时器是不是超时

(2)it_interval需要设置为0,timer_gettime获取的是定时器剩余的时间,如果不设置为0的话,那么定时器超时之后,会再一次进行计时,那么线程很难捕获到its.it_value.tv_sec == 0 && its.it_value.tv_nsec == 0条件满足的情况。

    struct itimerspec its;
    its.it_value.tv_sec = 3;
    its.it_value.tv_nsec = 0;
    its.it_interval.tv_sec = 0;
    its.it_interval.tv_nsec = 0;

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>#define NUM_TIMERS 3
typedef void (*CB)();
CB timer_cb[NUM_TIMERS];
timer_t timer_id[NUM_TIMERS];void cb1() {printf("timer 1\n");
}void cb2() {printf("timer 2\n");
}void cb3() {printf("timer 3\n");
}int create_timer(int seq, CB cb) {timer_t timerid;struct sigevent sev;sev.sigev_notify = SIGEV_NONE;sev.sigev_value.sival_ptr = &timerid;if (timer_create(CLOCK_BOOTTIME, &sev, &timerid) == -1) {perror("timer_create");return -1;}struct itimerspec its;its.it_value.tv_sec = 3;its.it_value.tv_nsec = 0;its.it_interval.tv_sec = 0;its.it_interval.tv_nsec = 0;if (timer_settime(timerid, 0, &its, NULL) == -1) {perror("timer_settime");return -1;}printf("seq %d, cb %p\n", seq, cb);cb();timer_cb[seq] = cb;timer_id[seq] = timerid;return 0;
}void* timer_manager() {while (1) {for (int i = 0; i < NUM_TIMERS; i++) {struct itimerspec its;if (timer_gettime(timer_id[i], &its) == -1) {perror("timer_gettime");continue;}if (its.it_value.tv_sec == 0 && its.it_value.tv_nsec == 0) {printf("call timer call back, cb %p\n", timer_cb[i]);timer_cb[i]();its.it_value.tv_sec = 3;its.it_value.tv_nsec = 0;timer_settime(timer_id[i], 0, &its, NULL);}}usleep(4 * 1000);}return NULL;
}int main() {create_timer(0, cb1);create_timer(1, cb2);create_timer(2, cb3);pthread_t thread_id;if (pthread_create(&thread_id, NULL, timer_manager, NULL) != 0) {perror("pthread_create");exit(EXIT_FAILURE);}sleep(60);for (int i = 0; i < NUM_TIMERS; i++) {timer_delete(timer_id[i]);}return 0;
}

3.2SIGEV_THREAD

如下是使用SIGEV_THREAD触发方式,创建了两个定时器,周期分别为5s和3s,定时器回调函数分别为timer_cb5和timer_cb3,在回调函数中打印了线程id。

如下是运行时的打印,从打印可以看出来,定时器函数每次调用,都会创建一个新的线程。

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>#define _GNU_SOURCE
#include <unistd.h>
#include <sys/types.h>void timer_cb5(union sigval sigev_value) { printf("timer callback 5, tid %d\n", gettid()); }
void timer_cb3(union sigval sigev_value) { printf("timer callback 3, tid %d\n", gettid()); }void create_timer(int period_in_second, void *cb) {struct sigevent sev;struct itimerspec its;timer_t timer_id;sev.sigev_notify = SIGEV_THREAD;sev.sigev_notify_function = cb;sev.sigev_notify_attributes = NULL;timer_create(CLOCK_BOOTTIME, &sev, &timer_id);its.it_interval.tv_sec = period_in_second;its.it_interval.tv_nsec = 0;its.it_value.tv_sec = period_in_second;its.it_value.tv_nsec = 0;timer_settime(timer_id, 0, &its, NULL);
}int main() {create_timer(5, timer_cb5);create_timer(3, timer_cb3);sleep(100);return 0;
}

3.3SIGEV_THREAD_ID

SIGEV_THREAD中的线程是posix库内部创建的线程,并且定时器的吗诶个周期都是创建一个线程,销毁一个线程。

SIGEV_THREAD_ID使用的是用户创建的线程,这样便于用户进行精确控制。在实际使用中,定时器是有精度误差的,特别是在linux这种非确定的操作系统中,用户态的定时器还受调度精度影响,并且误差不可预测。如果我们对某个定时器的精度要求高,那么可以将定时器线程设置为实时调度策略,来优化精度表现。

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <time.h>
#include <unistd.h>#define _GNU_SOURCE
#include <sys/types.h>void sig_handler(int a, siginfo_t *b, void *c) {printf("signal handler, tid %d, signal %d\n", gettid(), a);return;
}void create_timer(int signum, int period_in_second, void *cb) {struct sigaction sa;sa.sa_flags = 0;sa.sa_sigaction = cb;sigemptyset(&sa.sa_mask);sigaction(signum, &sa, NULL);sigevent_t event;timer_t timerid;event.sigev_notify = SIGEV_THREAD_ID;event.sigev_signo = signum;event._sigev_un._tid = gettid();event.sigev_value.sival_ptr = NULL;timer_create(CLOCK_BOOTTIME, &event, &timerid);struct itimerspec its;its.it_interval.tv_sec = period_in_second;its.it_interval.tv_nsec = 0;its.it_value.tv_sec = period_in_second;its.it_value.tv_nsec = 0;timer_settime(timerid, 0, &its, NULL);
}void *thread_func(void *arg) {create_timer(34, 3, sig_handler);create_timer(34, 5, sig_handler);while(1);return NULL;
}int main() {pthread_t thread;pthread_create(&thread, NULL, thread_func, NULL);sleep(100);return 0;
}

http://www.ppmy.cn/news/1544032.html

相关文章

Word设置只读后,为什么还能编辑?

Word文档设置了只读模式&#xff0c;是可以编辑的&#xff0c;但是当我们进行保存的时候就会发现&#xff0c;word提示需要重命名并选择新路径才能够保存。 这种操作&#xff0c;即使可以编辑文字&#xff0c;但是原文件是不会受到影响的&#xff0c;编辑之后的word文件会保存到…

前端之html(二)加入css开篇(一)

1.lebal标签-增大点击范围 性别:<input type"radio" name"gender" id"man"><lebal for"man">男</lebal> <lebal><input type"radio" name"gender" id"nv">女</leba…

基于SpringBoot的物品分类识别管理系统uniapp源码带文档教程

功能模块&#xff1a;垃圾分类&#xff08;垃圾分类题库&#xff0c;关键词搜索次数&#xff09;&#xff0c;系统管理等 技术框架&#xff1a;SpringBoot2 Mysql5.7 Mybatis-Plus uniapp Swagger2 RuoYi-fast swagger-bootstrap-ui 运行环境&#xff1a;jdk8 Intell…

Linux文件清空的五种方法总结分享

简介&#xff1a; 每种方法各有优势&#xff0c;选择最合适的一种或几种&#xff0c;可以极大提高您的工作效率。更多有关Linux系统管理的技巧与资源&#xff0c;欢迎访问&#xff0c;持续提升您的运维技能。 在Linux操作系统环境下&#xff0c;清空文件内容是日常维护和管理中…

怎么在哔哩哔哩保存完整视频

哔哩哔哩(B站)作为一个集视频分享、弹幕互动于一体的平台&#xff0c;吸引了大量用户。许多人希望能够将自己喜欢的完整视频保存到本地&#xff0c;以便离线观看或分享。直接下载视频的功能并不总是可用&#xff0c;因此&#xff0c;本文将介绍几种在哔哩哔哩上保存完整视频的方…

一些硬件知识【2024/11/2】

当需要提供功率型的输出信号的时候&#xff0c;可以在信号发生器外接功率放大器&#xff0c;这样可以提高输出功率 信号的调幅&#xff08;AM&#xff09;、调频&#xff08;FM&#xff09;与调相&#xff08;PM&#xff09;&#xff1a; 调制信号&#xff1a;控制高频振荡的低…

第十九章 Vue组件之data函数

目录 一、引言 二、示例代码 2.1. 工程结构图 2.2. main.js 2.3. App.vue 2.4. BaseCount.vue 三、运行效果 一、引言 在Vue CLI脚手架中一个组件的data选项必须是一个函数&#xff0c;以此保证每个组件实例&#xff0c;维护独立的一份数据对象。每次创建新的组件实…

Java后端面试内容总结

先讲项目背景&#xff0c;再讲技术栈模块划分&#xff0c; 讲业务的时候可以先讲一般再特殊 为什么用这个&#xff0c;好处是什么&#xff0c;应用场景 Debug发现问题/日志发现问题. QPS TPS 项目单元测试&#xff0c;代码的变更覆盖率达到80%&#xff0c;项目的复用性高…