当前位置: 首页 > news >正文

基于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!!!icon-default.png?t=N7T8https://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!

相关文章:

  • 【Hive中常见的优化手段----数据采集!Join 优化!Hive索引!数据倾斜!mapreduce本地模式!map和reduce数量调整!】
  • 【大模型LLM面试合集】大语言模型基础_llm概念
  • 由监官要求下架docker hub镜像导致无法正常拉取镜像
  • qt QTreeView的简单使用(多级子节点)
  • SpringCloud分布式微服务链路追踪方案:Skywalking
  • 从0开始学习pyspark--pyspark的核心概念[第0节]
  • DFS,BFS最短路,树与图的深度/广度优先遍历,拓扑排序
  • 为什么Vim是程序员最喜欢的文本编辑器之一?
  • 对象被优化以后才是高效的C++编程
  • 达梦数据库 页大小与数据库字段长度的关系
  • LeetCode题解:205. 同构字符串,哈希表,JavaScript,详细注释
  • 第一周java。2
  • 桥接模式与适配器模式
  • QT截屏,截取控件为图片,指定范围截屏三种截屏方式
  • 【HTML入门】第一课 - 网页标签框架
  • @jsonView过滤属性
  • java概述
  • js操作时间(持续更新)
  • Just for fun——迅速写完快速排序
  • Linux链接文件
  • PAT A1092
  • Python 基础起步 (十) 什么叫函数?
  • vue从创建到完整的饿了么(11)组件的使用(svg图标及watch的简单使用)
  • 京东美团研发面经
  • 看域名解析域名安全对SEO的影响
  • 如何进阶一名有竞争力的程序员?
  • 入门到放弃node系列之Hello Word篇
  • 深入浏览器事件循环的本质
  • 深入浅出Node.js
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • 你对linux中grep命令知道多少?
  • 组复制官方翻译九、Group Replication Technical Details
  • (1)Map集合 (2)异常机制 (3)File类 (4)I/O流
  • (8)Linux使用C语言读取proc/stat等cpu使用数据
  • (HAL库版)freeRTOS移植STMF103
  • (Matlab)使用竞争神经网络实现数据聚类
  • (八)光盘的挂载与解挂、挂载CentOS镜像、rpm安装软件详细学习笔记
  • (第8天)保姆级 PL/SQL Developer 安装与配置
  • (附源码)计算机毕业设计ssm-Java网名推荐系统
  • (免费领源码)Python#MySQL图书馆管理系统071718-计算机毕业设计项目选题推荐
  • (四)汇编语言——简单程序
  • (原创)攻击方式学习之(4) - 拒绝服务(DOS/DDOS/DRDOS)
  • (转)Unity3DUnity3D在android下调试
  • (转)德国人的记事本
  • .apk 成为历史!
  • .L0CK3D来袭:如何保护您的数据免受致命攻击
  • .Net CoreRabbitMQ消息存储可靠机制
  • .Net的DataSet直接与SQL2005交互
  • .NET构架之我见
  • .NET国产化改造探索(一)、VMware安装银河麒麟
  • .vimrc 配置项
  • // an array of int
  • [ Linux ] Linux信号概述 信号的产生
  • [ Linux 长征路第五篇 ] make/Makefile Linux项目自动化创建工具
  • [Android]通过PhoneLookup读取所有电话号码