timerfd加epoll封装定时器

news2024/11/24 16:38:48

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

文章目录

  • 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;//单独起一个线程,进行轮询epoll
    ThreadPool& 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;
			else
				thread_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 = obj

CXX := 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.coloradmin.cn/o/1666683.html

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

相关文章

临近空间相关概念

临近空间概念 距地 20KM-100KM 的临近空间位于内外层空间之中&#xff0c;也称为 超高空、近空间、亚轨道等。 特点就是&#xff1a;纵跨 非电离层和电离层、空气稀薄&#xff0c;存在 臭氧、紫外、辐射等特殊环境 存在 重力波、行星波、大气放电等特殊现象。 临近空间高速飞…

YOLOv8+CLIP实现图文特征匹配

本文通过结合YOLOv8s的高效物体检测能力与CLIP的先进图像-文本匹配技术&#xff0c;展示了深度学习在处理和分析复杂多模态数据中的潜力。这种技术的应用不仅限于学术研究&#xff0c;还能广泛应用于工业、商业和日常技术产品中&#xff0c;以实现更智能的人机交互和信息处理。…

[BJDCTF2020]ZJCTF,不过如此 1

涉及&#xff1a;php的伪协议、preg_replace函数的漏洞和正则表达式的运用。 解题步骤 <?phperror_reporting(0); $text $_GET["text"]; $file $_GET["file"]; if(isset($text)&&(file_get_contents($text,r)"I have a dream"))…

璩静霸道言论引发百度风波随笔

从5月9日晚开始有关“百度副总裁璩静已从公司离职”的消息&#xff0c;仅两天时间就几乎布满互联网所有知名自媒体平台&#xff0c;可谓兹事体大&#xff0c;无异于互联网发生了一场八级地震&#xff0c;波及面之广&#xff0c;匪夷所思&#xff01; 百度截图 尽管笔者一直密切…

|Python新手小白中级教程|第二十八章:面向对象编程(类定义语法私有属性类的继承与多态)(4)

文章目录 前言一、类定义语法二、私有方法和私有属性1.私有属性2.私有方法 三、类“继承”1.初识继承2.使用super函数调用父类中构造的东西 四、类“多态”1.多态基础2.子类不同形态3.使用isinstance函数与多态结合判断类型 总结 前言 大家好&#xff0c;我是BoBo仔吖&#xf…

Linux/Ubuntu下使用VS Code配置C/C++项目环境调用OpenCV

OpenCV是大型的Third party 计算机视觉库&#xff0c;在开发中会经常用到&#xff0c;本篇记录一下 在Ubuntu系统上安装和配置OpenCV&#xff0c;并使用C/C调用OpenCV 关于VS Code配置C/C开发环境的部分&#xff0c;见之前的博文 Linux/Ubuntu系统下使用VS Code配置C/C开发环境…

动态规划算法练习——计数问题

题目描述 给定两个整数 a 和 b&#xff0c;求 a 和 b 之间的所有数字中 0∼9 的出现次数。 例如&#xff0c;a1024&#xff0c;b1032&#xff0c;则 a 和 b 之间共有 9 个数如下&#xff1a; 1024 1025 1026 1027 1028 1029 1030 1031 1032 其中 0 出现 10 次&#xff0c;1 出现…

360度全景航拍生成原创,玩命增粉10W ,月入万余元【视频教学 配套设施专用工具】

抖音近期推出了一种全新的玩法&#xff0c;那就是360度全景航拍&#xff0c;这为原创者们带来了新的增粉机会&#xff0c;有望在一个月内收入过万。这个新玩法配有视频教学和专用工具。 项目 地 址 &#xff1a; laoa1.cn/1993.html 抖音的这个新功能&#xff0c;就是360度全…

南京观海微电子----开关电流与输入输出电流的关系

BOOST 结构的工作原理及波形 BOOST 结构简单原理图见图 1&#xff0c;工作时各点的电压电流波形见图 2。 不考虑上电时的情形&#xff0c;仅考虑稳定工作时&#xff0c;情况如下&#xff1a; 当开关管 Q 导通时&#xff08;开关管电压为 0&#xff09;&#xff0c;电感 L 相当…

【密评】 | 商用密码应用安全性评估从业人员考核题库(9/58)

Hill密码是重要古典密码之一&#xff0c;其加密的核心思想的是&#xff08;&#xff09;。 A.线性变换 B.非线性变换 C.循环移位 D.移位 著名的Kerckhoff原则是指&#xff08;&#xff09;。 A.系统的保密性不但依赖于对加密体制或算法的保密&#xff0c;而且依赖于密钥 B.系统…

【计算机网络】数据链路层的功能

数据链路层的基本功能&#xff1a; 封装成帧透明传输差错检测 数据链路层使用的信道主要有两种 点对点信道——PPP协议广播信道——CSMA/CD协议(有线局域网)、CSMA/CA协议(无线局域网) 数据链路层所处的地位 从图中可以看出&#xff0c;数据从主机H1送到主机H2需要在路径中…

C#【进阶】泛型

1、泛型 文章目录 1、泛型1、泛型是什么2、泛型分类3、泛型类和接口4、泛型方法5、泛型的作用思考 泛型方法判断类型 2、泛型约束1、什么是泛型2、各泛型约束3、约束的组合使用4、多个泛型有约束思考1 泛型实现单例模式思考2 ArrayList泛型实现增删查改 1、泛型是什么 泛型实现…

Autoxjs 实践-Spring Boot 集成 WebSocket

概述 最近弄了福袋工具&#xff0c;由于工具运行中&#xff0c;不好查看福袋结果&#xff0c;所以我想将福袋工具运行数据返回到后台&#xff0c;做数据统计、之后工具会越来越多&#xff0c;就弄了个后台&#xff0c;方便管理。 实现效果 WebSocket&#xff1f; websocket是…

【JavaEE初阶系列】——Cookie和Session应用之实现登录页面

目录 &#x1f6a9;本章目标 1.登录页面 2.servlet处理上述的登录请求 3.网站主页(成功登录之后的页面&#xff09; &#x1f6a9;实现过程 &#x1f393;登录页面 &#x1f393;Servlet处理登录请求 &#x1f388;获取请求传来的参数(用户名和密码) &#x1f388;验证…

Electron学习笔记(五)

文章目录 相关笔记笔记说明 七、系统1、系统对话框2、自定义窗口菜单3、系统右键菜单4、快捷键(1)、监听网页按键事件 &#xff08;窗口需处于激活状态&#xff09;(2)、监听全局按键事件 &#xff08;窗口无需处于激活状态&#xff09;(3)、补充&#xff1a;自定义窗口菜单快捷…

Three.js基础练习——渲染一个立方体

1.学习内容参考了 three.js入门教程--零基础也能学会_threejs菜鸟教程-CSDN博客 本章内容包含渲染立方体&#xff0c;并配合ui工具食用~ 2.效果图 import * as THREE from three import * as dat from dat.gui import { OrbitControls } from three/addons/controls/OrbitC…

【网站项目】SpringBoot803房屋租赁管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

【Golang】VSCode进行GO的调试

原来的launch.json {"version": "0.2.0","configurations": [{"name": "Golang","type": "go","request": "launch","program": "${workspaceFolder}","…

Linux技术---部署PXE服务器实现批量安装操作系统

部署PXE服务器实现批量安装操作系统 部署PXE服务器实现批量安装操作系统 部署PXE服务器实现批量安装操作系统1.安装相关服务组件1.1 安装tftp和xinetd1.2 安装DHCP服务1.3 准备 Linux 内核、初始化镜像文件、 PXE 引导程序、安装FTP服务并准备安装源1.4 配置启动菜单文件1.5 验…

JVM之运行时数据区

Java虚拟机在运行时管理的内存区域被称为运行时数据区。 程序计数器&#xff1a; 也叫pc寄存器&#xff0c;每个线程会通过程序计数器记录当前要执行的字节码指令的地址。程序计数器在运行时是不会发生内存溢出的&#xff0c;因为每个线程只存储一个固定长度的内存地址。 JAVA虚…