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.pswp.cn/web/10652.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/10652.shtml
英文地址,請注明出處:http://en.pswp.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…

Shell參數擴展形式學習筆記

Shell參數擴展形式學習筆記 文章目錄 Shell參數擴展形式學習筆記空值判斷處理 ${parameter:-word} ${parameter:word} ${parameter:?word} ${parameter:word}變量位置截取 ${parameter:offset} ${parameter:offset:length}變量匹配組合 ${!prefix*} ${!prefix} ${!name[]} ${!…

感知機和神經網絡

引入 什么是神經網絡&#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)); …

連接和斷開與服務器的連接

要連接到服務器&#xff0c;通常需要在調用mysql時提供一個MySQL用戶名&#xff0c;很可能還需要一個密碼。如果服務器在除了登錄的計算機之外的機器上運行&#xff0c;您還必須指定主機名。聯系您的管理員以找出應該使用哪些連接參數來連接&#xff08;即使用哪個主機、用戶名…

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;該系統能夠實時監測并顯示室內環境的溫濕度、甲醛濃度以及二氧化碳濃…

C語言題目:A+B for Input-Output Practice

題目描述 Your task is to calculate the sum of some integers 輸入格式 Input contains an integer N in the first line, and then N lines follow. Each line starts with a integer M, and then M integers follow in the same line 輸出格式 For each group of inpu…

Sass詳解

Sass&#xff08;Syntactically Awesome Stylesheets&#xff09;是一種CSS預處理器&#xff0c;它允許你使用變量、嵌套規則、混入&#xff08;Mixin&#xff09;、繼承等功能來編寫CSS&#xff0c;從而使CSS代碼更加簡潔、易于維護和擴展。下面是Sass的詳細解釋&#xff1a; …

【docker】容器優化:一行命令換源

原理&#xff1a; 根據清華源提供的Ubuntu 軟件倉庫進行sources.list替換 ubuntu | 鏡像站使用幫助 | 清華大學開源軟件鏡像站 | Tsinghua Open Source Mirror 1、換源 echo "">/etc/apt/sources.list \&& echo "# 默認注釋了源碼鏡像以提高 apt …

新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…

Dockerfile中的CMD和ENTRYPOINT

Shell格式和Exec格式 在Dockerfile中&#xff0c;RUN、CMD和ENTRYPOINT指令都可以使用兩種格式&#xff1a;Shell格式和Exec格式。 exec 格式&#xff1a;INSTRUCTION ["executable","param1","param2"] shell 格式&#xff1a; INSTRUCTION c…