timerfd加epoll封装定时器

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 1、用timerfd加epoll封装定时器的优点
  • 2、代码实现

1、用timerfd加epoll封装定时器的优点

定时器为什么需要timerfd
在设计定时器时,我们首先想到的就是设置一个定时任务触发的时间,然后不断判断(死循环)当前时间是否大于等于定时任务触发的时间,如果是,那么就处理定时任务。这就是最为简单的设计,在我之前的博客中[定时器的简单实现],就是这么实现的,但是这样设计会存在诸多问题

  1. CPU资源浪费
    使用死循环来检查时间意味着CPU必须不断地执行这段代码,即使大部分时间都是在做无用的比较。这会导致CPU资源的浪费,尤其是在高性能的服务器或多任务环境中。
  2. 响应性下降
    由于CPU忙于执行定时器的检查,它可能无法及时响应其他重要的事件或任务,导致系统响应性下降。
  3. 不准确
    依赖于系统的时钟分辨率和调度器延迟,使用死循环检查时间的方法可能无法实现精确的定时。例如,如果系统时钟的分辨率是毫秒级,而你尝试实现微秒级的定时,那么这种方法就无法满足需求。
  4. 不适合长时间等待
    如果定时任务触发的时间间隔很长(例如几小时或几天),那么使用死循环来等待这段时间是非常低效的。

为解决上述问题,就产生了timerfd,当使用timerfd_create创建timerfd时,并设置了定时任务,当定时任务时间到达时,那么timerfd就变成了可读,经常与 select/poll/epoll搭配使用

这里我们不需要轮询这个timerfd,判断timerfd是否有数据(是否可读),因为这样做也会带来上述问题,因此我们需要将timerfd加入到select/poll/epoll中,让它们轮询,一般来说使用epoll更高效

  1. 统一的事件处理:epoll是Linux下多路复用IO接口select/poll的增强版本,它可以高效地处理大量的文件描述符和I/O事件。通过将timerfd的文件描述符加入epoll的监控集合中,可以将定时器超时事件与其他I/O事件进行统一处理,简化了事件驱动编程的复杂性。
  2. 提高并发性能:在高并发的网络服务器中,使用epoll可以监控多个套接字的I/O事件,而使用timerfd可以实现定时任务(如心跳检测、超时处理等)。这种结合使用的方式可以提高系统的并发性能和吞吐量。
  3. 减少系统调用开销:由于epoll采用I/O多路复用机制,并且只在有事件发生时才进行通知,因此可以减少不必要的系统调用开销。同时,由于timerfd的精度较高,可以减少因轮询而产生的额外开销。

2、代码实现

定时任务

//TimerEvent.h
#pragma once
#include <cstdint>
#include <functional>
#include <sys/time.h>
#include <memory>
class TimerEvent
{
public:using s_ptr = std::shared_ptr<TimerEvent>;template<typename F, typename... Args>TimerEvent(int interval, bool is_repeated, F&& f, Args&&... args):interval_(interval), is_repeated_(is_repeated){auto task = std::bind(std::forward<F>(f), std::forward<Args>(args)...);task_ = task;}int64_t getArriveTime() const{return arrive_time_;}void setCancler(bool flag){is_cancled_ = flag;}bool isCancle(){return is_cancled_;}bool isRepeated(){return is_repeated_;}std::function<void()> getCallBack(){return task_;}//重新设定任务到达时间void resetArriveTime();//获取当前时间static int64_t getNowMs();
private:int64_t arrive_time_;//ms 执行任务时毫秒级时间戳,达到对应的时间戳就执行对应的任务int64_t interval_;//ms 隔多少ms后执行bool is_repeated_{false};//是否为周期性的定时任务bool is_cancled_{false};//是否取消std::function<void()> task_;
};//TimerEvent.cpp
#include"TimerEvent.h"int64_t TimerEvent::getNowMs()
{timeval val;gettimeofday(&val, NULL);return val.tv_sec*1000 + val.tv_usec/1000;
}void TimerEvent::resetArriveTime()
{arrive_time_ = getNowMs() + interval_;
}

对timerfd的封装

//Timer.h
#pragma once
#include <map>
#include <vector>
#include <iostream>
#include "TimerEvent.h"class Timer
{
public:Timer();~Timer();int getFd(){return fd_;}void addTimerEvent(TimerEvent::s_ptr event);void deleteTimerEvent(TimerEvent::s_ptr event);//时间到达就触发void onTimer();std::vector<std::function<void()>> &getCallbacks(){return callbacks_;}//重新设置任务的到达时间void resetArriveTime();private:int fd_;std::multimap<int64_t, TimerEvent::s_ptr> pending_events_;std::vector<std::function<void()>> callbacks_;
};//Timer.cpp
#include <sys/timerfd.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include "Timer.h"Timer::Timer() : fd_(timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK))
{
}Timer::~Timer()
{}void Timer::resetArriveTime()
{if (pending_events_.empty()){return;}int64_t now = TimerEvent::getNowMs();auto it = pending_events_.begin();int64_t inteval = 0;// 第一个任务的定时时间比当前时间大,则重新设置if (it->second->getArriveTime() > now){inteval = it->second->getArriveTime() - now;}else{// 第一个任务的定时时间比当前时间小或相等,说明第一个任务已经超时了,应该立马执行该任务inteval = 100; // ms}timespec ts;memset(&ts, 0, sizeof(ts));ts.tv_sec = inteval / 1000;//秒ts.tv_nsec = (inteval % 1000) * 1000000;//纳秒itimerspec value;memset(&value, 0, sizeof(value));value.it_value = ts;int result = timerfd_settime(fd_, 0, &value, NULL);if (result != 0){printf("timerfd_settime error, errno=%d, error=%s", errno, strerror(errno));}
}void Timer::addTimerEvent(TimerEvent::s_ptr event)
{bool is_reset_timerfd = false;if (pending_events_.empty()){is_reset_timerfd = true;}else{auto it = pending_events_.begin();// 当前需要插入的定时任务时间比已经存在的定时任务时间要早,那么就需要重新设定超时时间,防止任务延时if (it->first > event->getArriveTime()){is_reset_timerfd = true;}}pending_events_.emplace(event->getArriveTime(), event);if (is_reset_timerfd){resetArriveTime();}
}void Timer::deleteTimerEvent(TimerEvent::s_ptr event)
{event->setCancler(true);//pending_events_是multimap,key是时间,可能存在多个相同时间的event//将对应的event从pending_events_中删除auto begin = pending_events_.lower_bound(event->getArriveTime());auto end = pending_events_.upper_bound(event->getArriveTime());auto it = begin;for(;it != end; ++it){if(it->second == event){break;}}if(it != end){pending_events_.erase(it);}}void Timer::onTimer()
{char buf[8];for(;;){if((read(fd_, buf, 8) == -1) && errno == EAGAIN){break;}}int64_t now = TimerEvent::getNowMs();std::vector<TimerEvent::s_ptr> tmps;std::vector<std::function<void()>>& callbacks_ = getCallbacks();auto it = pending_events_.begin();for(; it != pending_events_.end(); ++it){// 任务已经到时或者超时,并且没有被取消,就需要执行if((it->first <= now) && !it->second->isCancle()){tmps.push_back(it->second);callbacks_.push_back(it->second->getCallBack());}else{break;// 因为定时任务是升序排的,只要第一个任务没到时,后面的都没到时}}//因为把任务已经保存好了,因此需要把m_pending_events中对应的定时任务删除,防止下次又执行了pending_events_.erase(pending_events_.begin(), it);// 需要把重复的TimerEvent再次添加进去for(auto i = tmps.begin(); i != tmps.end(); ++i){if(!(*i)->isCancle()){//std::cout<<"重新添加"<<std::endl;(*i)->resetArriveTime();addTimerEvent(*i);}}resetArriveTime();
}

对epoll的封装

//TimerPollPoller.h
#pragma once
#include <sys/epoll.h>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include <atomic>
#include <iostream>
#include "ThreadPool.h"
#include "Timer.h"class TimerPollPoller
{
public:TimerPollPoller(unsigned int num = std::thread::hardware_concurrency()):epollfd_(::epoll_create1(EPOLL_CLOEXEC)),thread_pool_(ThreadPool::instance()),stop_(true){timer_ = std::make_shared<Timer>();struct epoll_event event;memset(&event, 0, sizeof(event));event.data.ptr = reinterpret_cast<void*>(&timer_);event.events = EPOLLIN;::epoll_ctl(epollfd_, EPOLL_CTL_ADD, timer_->getFd(), &event);start();}~TimerPollPoller(){::close(epollfd_);stop();if(t.joinable()){std::cout << "主线程 join thread " << t.get_id() << std::endl;t.join();}}void start();void stop();void addTimerEvent(TimerEvent::s_ptr event);void cancelTimeEvent(TimerEvent::s_ptr event);void handleTimerfdInEpoll();
private:const int epollfd_;std::shared_ptr<Timer> timer_;std::thread t;//单独起一个线程,进行轮询epollThreadPool& thread_pool_;std::atomic<bool> stop_;
};//TimerPollPoller.cpp
#include "TimerPollPoller.h"void TimerPollPoller::start()
{t = std::move(std::thread(&TimerPollPoller::handleTimerfdInEpoll, this));
}
void TimerPollPoller::stop()
{stop_.store(true);
}
void TimerPollPoller::addTimerEvent(TimerEvent::s_ptr event)
{timer_->addTimerEvent(event);
}
void TimerPollPoller::cancelTimeEvent(TimerEvent::s_ptr event)
{timer_->deleteTimerEvent(event);
}
void TimerPollPoller::handleTimerfdInEpoll()
{struct epoll_event event;stop_.store(false);while(!stop_.load()){int numEvents = ::epoll_wait(epollfd_, &event, 1, 0);if(numEvents == 1){std::shared_ptr<Timer> timer_ptr = *reinterpret_cast<std::shared_ptr<Timer>*>(event.data.ptr);timer_ptr->onTimer();std::vector<std::function<void()>> callbacks = std::move(timer_ptr->getCallbacks());for(auto task:callbacks){thread_pool_.commit(task);}}}
}

处理任务的线程池

#pragma once
#include <atomic>
#include <condition_variable>
#include <future>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>
#include <functional>
class ThreadPool {
public:static ThreadPool& instance() {static ThreadPool ins;return ins;}using Task = std::packaged_task<void()>;~ThreadPool() {stop();}template <class F, class... Args>auto commit(F&& f, Args&&... args) -> std::future<decltype(f(args...))> {using RetType = decltype(f(args...));if (stop_.load())return std::future<RetType>{};auto task = std::make_shared<std::packaged_task<RetType()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));std::future<RetType> ret = task->get_future();{std::lock_guard<std::mutex> cv_mt(cv_mt_);//将任务放进任务队列中tasks_.emplace([task] { (*task)(); });}//唤醒一个线程cv_lock_.notify_one();return ret;}int idleThreadCount() {return thread_num_;}
private:ThreadPool(const ThreadPool&) = delete;ThreadPool& operator=(const ThreadPool&) = delete;ThreadPool(unsigned int num = std::thread::hardware_concurrency()): stop_(false) {{if (num < 1)thread_num_ = 1;elsethread_num_ = num;}start();}//启动所有线程void start() {for (int i = 0; i < thread_num_; ++i) {pool_.emplace_back([this]() {while (!this->stop_.load()) {Task task;{std::unique_lock<std::mutex> cv_mt(cv_mt_);this->cv_lock_.wait(cv_mt, [this] {//stop_为true或者tasks_不为空(return 返回true),则进行下一步,否则阻塞在条件变量上return this->stop_.load() || !this->tasks_.empty();});if (this->tasks_.empty())return;task = std::move(this->tasks_.front());this->tasks_.pop();}this->thread_num_--;task();this->thread_num_++;}});}}void stop() {stop_.store(true);cv_lock_.notify_all();for (auto& td : pool_) {if (td.joinable()) {std::cout << "join thread " << td.get_id() << std::endl;td.join();}}}
private:std::mutex               cv_mt_;std::condition_variable  cv_lock_;std::atomic_bool         stop_;std::atomic_int          thread_num_;std::queue<Task>         tasks_;std::vector<std::thread> pool_;
};

测试代码

#include "TimerPollPoller.h"
#include <iostream>
void print()
{std::cout << "I love psy" << std::endl;
}
void print1()
{std::cout << "I love fl" << std::endl;
}int main()
{TimerPollPoller timerPollPoller;TimerEvent::s_ptr timer1 = std::make_shared<TimerEvent>(500, true, print);TimerEvent::s_ptr timer2 = std::make_shared<TimerEvent>(1000, true, print1);timerPollPoller.addTimerEvent(timer1);timerPollPoller.addTimerEvent(timer2);std::this_thread::sleep_for(std::chrono::seconds(2));timerPollPoller.cancelTimeEvent(timer1);std::this_thread::sleep_for(std::chrono::seconds(2));return 0;
}

makefile

PATH_SRC := .
PATH_BIN = bin
PATH_OBJ = objCXX := g++
CXXFLAGS := -g -O0 -std=c++11 -lpthread -Wall -Wno-deprecated -Wno-unused-but-set-variable
CXXFLAGS += -I./SRCS := $(wildcard $(PATH_SRC)/*.cpp) 
OBJS := $(patsubst $(PATH_SRC)/%.cpp,$(PATH_OBJ)/%.o,$(SRCS))  TARGET := $(PATH_BIN)/main# 默认目标:生成可执行文件
all : $(TARGET)# 链接规则
$(TARGET): $(OBJS)$(CXX) $(CXXFLAGS) $(OBJS) -o $@ $(PATH_OBJ)/%.o: $(PATH_SRC)/%.cpp  $(CXX) $(CXXFLAGS) -c $< -o $@ clean:rm -rf $(PATH_OBJ)/*.o $(TARGET).PHONY : clean

在这里插入图片描述

使用之间,在当前目录下需要创建bin目录和obj目录,然后再进行make,就能在bin目录下生产可执行程序main

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/10652.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【SpringBoot】Redis Lua脚本实战指南:简单高效的构建分布式多命令原子操作、分布式锁

文章目录 一.Lua脚本1.Lua特性2.Lua优势 二.Lua语法1.注释2.变量3.数据类型&#xff1a;3.1.基本类型3.2.对象类型&#xff1a;表&#xff08;table&#xff09; 4.控制结构&#xff1a;4.1.条件语句: 使用if、else和elseif来实现条件分支。4.2.循环结构&#xff1a;Lua支持for…

感知机和神经网络

引入 什么是神经网络&#xff1f; 我们今天学习的神经网络&#xff0c;不是人或动物的神经网络&#xff0c;但是又是模仿人和动物的神经网络而定制的神经系统&#xff0c;特别是大脑和神经中枢&#xff0c;定制的系统是一种数学模型或计算机模型&#xff0c;神经网络由大量的人…

图像处理:图像噪声添加

文章目录 前言一、高斯噪声二、椒盐噪声三、泊松噪声四、斑点噪声五、指数噪声六、均匀噪声总结 前言 本文主要介绍几种添加图像噪声的方法&#xff0c;用于数据增强等操作。 以下图为例。 一、高斯噪声 高斯噪声就是给图片添加一个服从高斯分布的噪声&#xff0c;可以通过调…

vLLM初探

vLLM是伯克利大学LMSYS组织开源的大语言模型高速推理框架&#xff0c;旨在极大地提升实时场景下的语言模型服务的吞吐与内存使用效率。vLLM是一个快速且易于使用的库&#xff0c;用于 LLM 推理和服务&#xff0c;可以和HuggingFace 无缝集成。vLLM利用了全新的注意力算法「Page…

Python+PySpark数据计算

1、map算子 对RDD内的元素进行逐个处理&#xff0c;并返回一个新的RDD&#xff0c;可以使用lambda以及链式编程&#xff0c;简化代码。 注意&#xff1a;再python中的lambda只能有行&#xff0c;如果有多行&#xff0c;要写成外部函数&#xff1b;&#xff08;T&#xff09;-&…

train_gpt2_fp32.cu - cudaCheck

源码 // CUDA error checking void cudaCheck(cudaError_t error, const char *file, int line) {if (error ! cudaSuccess) {printf("[CUDA ERROR] at file %s:%d:\n%s\n", file, line,cudaGetErrorString(error));exit(EXIT_FAILURE);} }; 解释 该函数用于检查CU…

无人机路径规划:基于鲸鱼优化算法WOA的复杂城市地形下无人机避障三维航迹规划,可以修改障碍物及起始点(Matlab代码)

一、部分代码 close all clear clc rng(default); %% 载入数据 data.S[50,950,12]; %起点位置 横坐标与纵坐标需为50的倍数 data.E[950,50,1]; %终点点位置 横坐标与纵坐标需为50的倍数 data.Obstaclexlsread(data1.xls); data.numObstacleslength(data.Obstacle(:,1)); …

TypeError: can only concatenate str (not “int“) to str

TypeError: can only concatenate str (not "int") to str a 窗前明月光&#xff0c;疑是地上霜。举头望明月&#xff0c;低头思故乡。 print(str_len len(str_text) : len(a)) 试图打印出字符串 a 的长度&#xff0c;但是在 Python 中拼接字符串和整数需要使用字符…

【微服务】spring aop实现接口参数变更前后对比和日志记录

目录 一、前言 二、spring aop概述 2.1 什么是spring aop 2.2 spring aop特点 2.3 spring aop应用场景 三、spring aop处理通用日志场景 3.1 系统日志类型 3.2 微服务场景下通用日志记录解决方案 3.2.1 手动记录 3.2.2 异步队列es 3.2.3 使用过滤器或拦截器 3.2.4 使…

triton编译学习

一 流程 Triton-MLIR: 从DSL到PTX - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/671434808Superjomns blog | OpenAI/Triton MLIR 迁移工作简介https://superjom

基于STM32单片机的环境监测系统设计与实现

基于STM32单片机的环境监测系统设计与实现 摘要 随着环境污染和室内空气质量问题的日益严重&#xff0c;环境监测系统的应用变得尤为重要。本文设计并实现了一种基于STM32单片机的环境监测系统&#xff0c;该系统能够实时监测并显示室内环境的温湿度、甲醛浓度以及二氧化碳浓…

新iPadPro是怎样成为苹果史上最薄产品的|Meta发布AI广告工具全家桶| “碾碎一切”,苹果新广告片引争议|生成式AI,苹果倾巢出动

Remini走红背后&#xff1a;AI生图会是第一个超级应用吗&#xff1f;新iPadPro是怎样成为苹果史上最薄产品的生成式AI&#xff0c;苹果倾巢出动Meta发布AI广告工具全家桶&#xff0c;图像文本一键生成解放打工人苹果新iPadPro出货量或达500万台&#xff0c;成中尺寸OLED发展关键…

8、QT——QLabel使用小记2

前言&#xff1a;记录开发过程中QLabel的使用&#xff0c;持续更新ing... 开发平台&#xff1a;Win10 64位 开发环境&#xff1a;Qt Creator 13.0.0 构建环境&#xff1a;Qt 5.15.2 MSVC2019 64位 一、基本属性 技巧&#xff1a;对于Qlabel这类控件的属性有一些共同的特点&am…

QToolButton的特殊使用

QToolButton的特殊使用 介绍通过QSS取消点击时的凹陷效果点击时的凹陷效果通过QSS取消点击时的凹陷效果 介绍 该篇文章记录QToolButton使用过程中的特殊用法。 通过QSS取消点击时的凹陷效果 点击时的凹陷效果 通过QSS取消点击时的凹陷效果 #include <QToolButton> #i…

【深耕 Python】Quantum Computing 量子计算机(5)量子物理概念(二)

写在前面 往期量子计算机博客&#xff1a; 【深耕 Python】Quantum Computing 量子计算机&#xff08;1&#xff09;图像绘制基础 【深耕 Python】Quantum Computing 量子计算机&#xff08;2&#xff09;绘制电子运动平面波 【深耕 Python】Quantum Computing 量子计算机&…

ios 开发如何给项目安装第三方库,以websocket库 SocketRocket 为例

1.brew 安装 cococapods $ brew install cocoapods 2、找到xcode项目 的根目录&#xff0c;如图&#xff0c;在根目录下创建Podfile 文件 3、在Podfile文件中写入 platform :ios, 13.0 use_frameworks! target chat_app do pod SocketRocket end project ../chat_app.x…

Python实战开发及案例分析(18)—— 逻辑回归

逻辑回归是一种广泛用于分类任务的统计模型&#xff0c;尤其是用于二分类问题。在逻辑回归中&#xff0c;我们预测的是观测值属于某个类别的概率&#xff0c;这通过逻辑函数&#xff08;或称sigmoid函数&#xff09;来实现&#xff0c;该函数能将任意值压缩到0和1之间。 逻辑回…

【linux】详解linux基本指令

目录 cat more less head tail 时间 cal find grep zip/unzip tar bc uname –r 关机 小编一共写了两篇linux基本指令&#xff0c;这两篇涵盖了大部分初学者的必备指令&#xff0c;这是第二篇&#xff0c;第一篇详见http://t.csdnimg.cn/HRlVt cat 适合查看小文…

网站localhost和127.0.0.1可以访问,本地ip不可访问解决方案

部署了一个网站, 使用localhost和127.0.0.1加端口号可以访问, 但是使用本机的ip地址加端口号却不行. 原因可能有多种. 可能的原因: 1 首先要确认是否localhost对应的端口是通的(直接网址访问), 以及你无法访问的那个本机ip是否正确(使用ping测试)&#xff1b; 2 检查本机的防火…

从头理解transformer,注意力机制(下)

交叉注意力 交叉注意力里面q和KV生成的数据不一样 自注意力机制就是闷头自学 解码器里面的每一层都会拿着编码器结果进行参考&#xff0c;然后比较相互之间的差异。每做一次注意力计算都需要校准一次 编码器和解码器是可以并行进行训练的 训练过程 好久不见输入到编码器&…