基于C++实现的EventLoop与事件驱动编程
一,概念介绍
事件驱动编程(Event-Driven)是一种编码范式,常被应用在图形用户界面,应用程序,服务器开发等场景。
采用事件驱动编程的代码中,通常要有事件循环,侦听事件,以及不同事件所对应的回调函数。
事件驱动编程经常被应用在前端开发以及C++服务器开发等场景。
Event即事件,是事件驱动编程中的基本处理单元,可以理解为各种各样的信号,对于UI界面来说,鼠标点击、键盘输入、触摸屏输入都可以理解为事件。
事件循环模式(Event loop)是一种简单且高效的并发编程模式,当前业界有很多主流的C++编程框架比如libevent,libuv,Boost.Asio等都支持事件循环机制。但是考虑代码封装上的简洁,我们也可以借助C++11标准实现自己的事件循环代码。通过事件循环,程序可以支持非阻塞的异步操作,提高系统的性能。
步骤示意图:
拿Event填充Event队列:

客户端只管发起请求,触发相应的事件,其他步骤交给队列去处理:

EventLoop样例代码:
#include <algorithm>
#include <iostream>
#include <vector>
#include <map>class EventManager {
private:std::map<std::string, std::vector<void (*)(int)> > events;public:EventManager() {}EventManager* eventRegist(std::string event_name, void (*callback)(int)) {std::vector<void (*)(int)>* listeners = &events[event_name];// if this listener is already registered, we wont add it againif (std::find(listeners->begin(), listeners->end(), callback) != listeners->end()) {return this;}listeners->push_back(callback);return this;}bool emit(std::string event_name, int arg) {std::vector<void (*)(int)> listeners = events[event_name];if (listeners.size() == 0) return false;for (int idx = 0; idx < listeners.size(); idx += 1) {listeners[idx](arg);}return true;}
};void callback1(int num) {std::cout << "callback1-" << num << std::endl;
}
void callback2(int num) {std::cout << "callback2-" << num << std::endl;
}int main() {EventManager* event_manager = new EventManager();//注册回调函数event_manager->eventRegist("event1", callback1);event_manager->eventRegist("event2", callback2);//执行回调函数int eventA = event_manager->emit("event1", 10);int eventB = event_manager->emit("event2", 20);return 0;
}
运行结果:
callback1-10
callback2-20
根据以上代码样例,我们发现事件驱动编程通常有以下编码元素:
1.回调函数:回调函数可以是预定义的函数,也可以是匿名函数或Lambda表达式。
2.注册回调:将回调函数赋值给Event的一个std::function成员变量,再将Event添加到Event Loop对应的队列中。
3.触发Event对应的请求以后,从队列中执行事件对应的回调函数。
二,Event Loop步骤拆解
事件循环(Event loop)是一种轮询机制,这种轮询是异步的,有时候轮询和事件注册发生在不同的线程中。
事件循环特别适用于异步编程,在事件循环中,程序会不断地等待事件的发生,并根据事件的类型和优先级来执行相应的处理逻辑。
事件循环主要由以下四个部分组成:
1.事件队列(Event Queue):
用于存储待处理的事件,每个事件都包含一个回调函数和相应的函数参数。
2.事件触发器(Event Trigger):
负责监听外部事件(如用户输入、网络请求等),并将事件添加到事件队列中。
3.事件处理器(Event Handler):
从事件队列中取出对应事件,并执行事件的回调函数。
4.回调函数(Callback Function):
与特定事件相关联的函数,当对应的事件发生时才会被调用执行。回调函数只有被"注册"到事件队列中才会被调用执行。所谓的"注册"就是将回调函数赋值给Event对应的函数对象。
事件循环(Event Loop)是一个无限循环,它会不断地从事件队列中取出事件,并执行对应的回调函数。在有些模式下,事件循环会检查事件队列是否为空,如果为空则会进入休眠状态等待新的事件到来。
c++服务器开发教程
【腾讯T9推荐】2024最新linux c/c++后端服务器开发教程,通俗易懂深入底层讲解,多项目实战,1V1指导,学完轻松拿下大厂offer!!!
https://www.bilibili.com/video/BV1XZ421q7UD/
免费学习地址:c/c++ linux服务器开发/后台架构师
需要C/C++ Linux服务器架构师学习资料加qun579733396获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

事件循环的基本流程如下:
step.01:初始化事件队列。
step.02:进入循环,等待事件的发生。
step.03:当监听的事件被触发时,将事件添加到事件队列中。
step.04:从事件队列中取出一个事件,并异步执行对应的回调函数。
step.05:返回第2步,继续等待下一个事件的发生。
注意:step.01~step.05并不只发生在同一个线程中,很多时候,回调函数的调用会放在子线程中进行。
参考以上步骤,我们可以设计以下Event Loop代码:
#include <condition_variable>
#include <functional>
#include <thread>
#include <vector>
#include <iostream>class EventLoop
{
public:using callable_t = std::function<void()>;EventLoop() = default;~EventLoop() noexcept{enqueue([this]{m_running = false;});std::cout << "step.02: other thread print.\n";m_thread.join();}//禁用移动构造 & 拷贝构造EventLoop(const EventLoop&) = delete;EventLoop(const EventLoop&&) = delete;EventLoop& operator= (const EventLoop&) = delete;EventLoop& operator= (const EventLoop&&) = delete;void enqueue(callable_t&& callable) noexcept{{std::lock_guard<std::mutex> guard(m_mutex);m_writeBuffer.emplace_back(std::move(callable));}m_condVar.notify_one();}
private:std::vector<callable_t> m_writeBuffer;std::mutex m_mutex;std::condition_variable m_condVar;bool m_running{ true };std::thread m_thread{ &EventLoop::threadFunc, this};void threadFunc() noexcept{std::vector<callable_t> readBuffer;while (m_running){{std::unique_lock<std::mutex> lock(m_mutex);m_condVar.wait(lock, [this]{return !m_writeBuffer.empty();});std::swap(readBuffer, m_writeBuffer);}for (callable_t& func : readBuffer){func();}readBuffer.clear();}std::cout << "step.03: event loop end.\n";}
};int main()
{EventLoop eventLoop;eventLoop.enqueue([]{std::cout << "Event_01 is running.\n";});eventLoop.enqueue([]{std::cout << "Event_02 is running.\n";});eventLoop.enqueue([]{std::cout << "Event_03 is running.\n";});std::cout << "step.01: main thread print.\n";
}
运行结果:
step.01: main thread print.
step.02: other thread print.
Event_01 is running.
Event_02 is running.
Event_03 is running.
step.03: event loop end.
三,事件驱动代码实战
Demo1:没有添加Event Loop,主要运行Callback回调函数
#include <iostream>
#include <functional>
#include <string>
// 定义回调函数类型
typedef std::function<void(std::string str)> Callback;
// 模拟用户输入事件
void simulateUserInput(Callback callback_func) {std::string input;std::cout << "请输入一段文字:";getline(std::cin, input);callback_func(input); // 触发回调函数
}
// 处理用户输入事件的回调函数
void handleUserInput(std::string inputStr) {std::cout << "用户输入事件已触发!" << std::endl;std::cout << "用户输入的是: " << inputStr << std::endl;return;
}int main() {simulateUserInput(handleUserInput);return 0;
}
运行结果:
请输入一段文字:hello
用户输入事件已触发!
用户输入的是:hello
Demo2:
#include <iostream>
#include <functional>
#include <queue>
//定义事件类型
typedef std::function<void()> Event;
//事件队列
std::queue<Event> eventQueue;
//注册回调函数
void registerEventHandler(Event event) {eventQueue.push(event);
}
//事件处理器
void processEvents() {while (!eventQueue.empty()) {Event event = eventQueue.front();event(); //调用回调函数eventQueue.pop();}
}
//回调函数
void callback1() {std::cout << "Callback 1 called" << std::endl;
}
void callback2() {std::cout << "Callback 2 called" << std::endl;
}
int main() {//注册回调函数到事件队列registerEventHandler(callback1);registerEventHandler(callback2);//处理事件processEvents();return 0;
}
运行结果:
Callback 1 called
Callback 2 called
Demo3:
#include <iostream>
#include <functional>
#include <queue>
//定义Event结构体
struct Event {std::function<void()> callback;
};
//定义事件处理器
class EventHandler {
public:void handleEvent(Event event) {event.callback();}
};
//定义事件循环
class EventLoop {
public:void addEvent(Event event) {eventQueue.push(event);}void run() {while (!eventQueue.empty()) {Event event = eventQueue.front();eventQueue.pop();eventHandler.handleEvent(event);}}
private:std::queue<Event> eventQueue;EventHandler eventHandler;
};
int main() {//创建事件循环对象EventLoop eventLoop;//回调函数std::function<void()> callback1 = []() {std::cout << "Event 1 triggered!" << std::endl;};std::function<void()> callback2 = []() {std::cout << "Event 2 triggered!" << std::endl;};//创建事件并添加到事件循环中Event event1{ callback1 };Event event2{ callback2 };eventLoop.addEvent(event1);eventLoop.addEvent(event2);//运行事件循环eventLoop.run();return 0;
}
运行结果:
Event 1 triggered!
Event 2 triggered!相关文章:
基于C++实现的EventLoop与事件驱动编程
一,概念介绍 事件驱动编程(Event-Driven)是一种编码范式,常被应用在图形用户界面,应用程序,服务器开发等场景。 采用事件驱动编程的代码中,通常要有事件循环,侦听事件,…...
Android高级面试_8_热修补插件化等
Android 高级面试:插件化和热修复相关 1、dex 和 class 文件结构 class 是 JVM 可以执行的文件类型,由 javac 编译生成;dex 是 DVM 执行的文件类型,由 dx 编译生成。 class 文件结构的特点: 是一种 8 位二进制字节…...
显卡GTX与RTX有什么区别?哪一个更适合玩游戏?
游戏发烧友们可能对游戏显卡并不陌生,它直接关系到游戏画面的流畅度、细腻程度和真实感。在众多显卡品牌中,英伟达的GTX和RTX系列显卡因其出色的性能而备受关注。 一、GTX与RTX的区别 架构差异 GTX系列显卡采用的是Pascal架构,这是英伟达在…...
QT自定义信号和槽函数
在QT中最重要也是必须要掌握的机制,就是信号与槽机制,在MFC上也就是类型的机制就是消息与响应函数机制 在QT中我们不仅要学会如何使用信号与槽机制,还要会自定义信号与槽函数,要自定义的原因是系统提供的信号,在一些情…...
Atcoder Beginner Contest 359
传送门 A - Count Takahashi 时间限制:2秒 内存限制:1024MB 分数:100分 问题描述 给定 N 个字符串。 第 i 个字符串 () 要么是 Takahashi 要么是 Aoki。 有多少个 i 使得 等于 Takahashi ? 限制 N 是整数。每个…...
无线通讯几种常规天线类别简介
天线对于无线模块来说至关重要,合适的天线可以优化通信网络,增加其通信的范围和可靠性。天线的选型对最后的模块通信影响很大,不合适的天线会导致通信质量下降。针对不同的市场应用,天线的材质、安置方式、性能也大不一样。下面简…...
最大团问题--回溯法
一、相关定义 给定一个无向图 ,其中 V 是图的顶点集,E图的边集 完全图:如果无向图中的任何一对顶点之间都有边,这种无向图称为完全图 完全子图:给定无向图 ,如果 ,且对应任意 且 ,则…...
MBSE之简单介绍
MBSE之简单介绍 文章目录 MBSE之简单介绍1. What is MBSE?2. MBSE 最佳实践 1. What is MBSE? Model-Based Systems Engineering (MBSE), a.k.a. Model-Based Systems Development (MBSD), is a Systems Engineering process paradigm that emphasizes t…...
基于ODPS解析字段值为JSON的情况
最近在使用ODPS数据库,其中一个字段他是用JSON存储的,但是我是需要JSON字符串中的一个属性值就行,刚好ODPS中有一个函数可以用来使用! 使用案例 select GET_JSON_OBJECT({"id":1,"name":"xiaobai"},$.name);…...
CesiumJS【Basic】- #020 加载glb/gltf文件(Primitive方式)
文章目录 加载glb/gltf文件(Primitive方式)1 目标2 代码实现3 资源文件加载glb/gltf文件(Primitive方式) 1 目标 使用Primitive方式加载glb/gltf文件 2 代码实现 import * as Cesium from "cesium";const viewer = new Cesium.Viewer...
2024黑盾杯复现赛题MISC部分
一、一个logo 一张png图片,查看颜色通道即可发现flag 二、 学会Office 最好用联想自带的excel工具查看,我用WPS打开未解出题目 这里会发现有隐藏信息 隐藏信息为宏加密 。去百度了解宏加密后,发现有俩个宏,一个加密一个解密 执…...
Linux0.12内核源码解读(5)-head.s
大家好,我是呼噜噜,好久没有更新old linux了,本文接着上一篇文章图解CPU的实模式与保护模式,继续向着操作系统内核的世界前进,一起来看看heads.s as86 与GNU as 首先我们得了解一个事实,在Linux0.12内核源…...
刷代码随想录有感(119):动态规划——打家劫舍III(树形dp)
题干: 代码: class Solution { public:vector<int>dp(TreeNode* cur){if(cur NULL)return vector<int>{0, 0};vector<int> left dp(cur -> left);vector<int> right dp(cur -> right);//偷int val1 cur -> val l…...
vivado CARRY_REMAP、CASCADE_HEIGHT
CARRY_REMAP opt_design-carry_remap选项可用于将单个carry*单元重新映射到LUT中 提高了布线的设计效果。使用-carry_remap选项时,仅 将单级进位链转换为LUT。CARRY_REMAP属性允许您 指定在优化过程中要转换的长度较大的进位链。 您可以使用控制任意长度的单个进位链…...
Ubuntu磁盘分区和挂载 虚拟机扩容 逻辑卷的创建和扩容保姆及教程
目录 1、VMware虚拟机Ubuntu20.04系统磁盘扩容 2、Linux的磁盘分区和挂载 3、创建逻辑卷和逻辑卷的扩容 1、VMware虚拟机Ubuntu20.04系统磁盘扩容 通过下图可以看出我们的根磁盘一共有20G的大小,现在我们把它扩容为30G 注:如果你的虚拟机有快照是无…...
【附精彩文章合辑】哈佛辍学小哥的创业经历【挑战英伟达!00 后哈佛辍学小哥研发史上最快 AI 芯片,比 H100 快 20 倍!】
前情提要 https://blog.csdn.net/weixin_42661676/article/details/140020491 哈佛辍学小哥的创业经历 一、背景与起步 这位哈佛辍学小哥,名为Chris Zhu,是一位华裔学生,他在2020年进入哈佛大学,攻读数学学士学位和计算机科学硕…...
Oracle CPU使用率过高问题处理
1.下载Process Explorer 2.打开Process Explorer,查看CPU使用情况最高的进程 3.双击该进程,查看详情 \ 4. 获取cpu使用最好的线程tid 5. 查询sql_id select sql_id from v$session where paddr in( select addr from v$process where spid in(1…...
pyqt的QWidgetList如何多选?如何按下Ctrl多选?
通过设置setSelectionMode(QAbstractItemView.MultiSelection),可以实现QWidgetList的多选。 但是上述结果不太符合我们需求。设置多选模式后,只需鼠标点击就可以选择多个条目。 我希望按下Ctrl键时才进行多选,仅鼠标单击的话,只进…...
【电路笔记】-MOSFET放大器
MOSFET放大器 文章目录 MOSFET放大器1、概述2、电路图3、电气特性3.1 ** I D = F ( V G S ) I_D=F(V_{GS}) ID=F(VGS)**特性3.2 I D = F ( V D S ) I_D=F(V_{DS}) ID=F(VDS)特性4、MOSFET放大器5、输入和输出电压6、电压增益7、总结1、概述 在前面的文章中,我们已经…...
Ubuntu 20.04安装显卡驱动、CUDA、Pytorch(2024.06最新)
文章目录 一、安装显卡驱动1.1 查看显卡型号1.2 根据显卡型号选择驱动1.3 获取下载链接1.4 查看下载的显卡驱动安装文件1.5 更新软件列表和安装必要软件、依赖1.6 卸载原有驱动1.7 禁用默认驱动1.8 安装lightdm显示管理器1.9 停止显示服务器1.10 在文本界面中,禁用X…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
第7篇:中间件全链路监控与 SQL 性能分析实践
7.1 章节导读 在构建数据库中间件的过程中,可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中,必须做到: 🔍 追踪每一条 SQL 的生命周期(从入口到数据库执行)&#…...
CVPR2025重磅突破:AnomalyAny框架实现单样本生成逼真异常数据,破解视觉检测瓶颈!
本文介绍了一种名为AnomalyAny的创新框架,该方法利用Stable Diffusion的强大生成能力,仅需单个正常样本和文本描述,即可生成逼真且多样化的异常样本,有效解决了视觉异常检测中异常样本稀缺的难题,为工业质检、医疗影像…...
机器学习的数学基础:线性模型
线性模型 线性模型的基本形式为: f ( x ) ω T x b f\left(\boldsymbol{x}\right)\boldsymbol{\omega}^\text{T}\boldsymbol{x}b f(x)ωTxb 回归问题 利用最小二乘法,得到 ω \boldsymbol{\omega} ω和 b b b的参数估计$ \boldsymbol{\hat{\omega}}…...
