基于C++实现的EventLoop与事件驱动编程

news2024/11/11 4:48:00

一,概念介绍

事件驱动编程(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 again
        if (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!

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

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

相关文章

Qt样式表及相关案例

一.Qt样式表介绍 Qt样式表是一个可以自定义部件外观的强大机制&#xff0c;样式表的概念、术语、语法均受到HTML的层叠样式表(Cascading Style Sheets,CSS)的启发。 样式表可通过QApplication::setStyleSheet()函数将其设置到整个应用程序上&#xff0c;也可以使用QWidget::se…

突然!某大客户核心凌晨突然崩溃....

这几天实在太忙&#xff0c;刚弄完文档。业务线的同事就找到我&#xff0c;说一个银行客户的核心系统昨晚出了故障&#xff0c;还没找到原因&#xff0c;希望能帮忙分析下。 从提供的信息来看是业务跑任务报错&#xff0c;遇到了Oracle-00600和ora-07445 错误。 Doing block re…

使用Python实现深度学习模型通常涉及以下几个步骤

学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把手教你开发炫酷的vbs脚本制作(完善中……&#xff09; 4、牛逼哄哄的 IDEA编程利器技巧(编写中……&#xff09; 5、面经吐血整理的 面试技…

2024年【焊工(初级)】实操考试视频及焊工(初级)考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 焊工&#xff08;初级&#xff09;实操考试视频根据新焊工&#xff08;初级&#xff09;考试大纲要求&#xff0c;安全生产模拟考试一点通将焊工&#xff08;初级&#xff09;模拟考试试题进行汇编&#xff0c;组成一…

Windows条件竞争提权漏洞复现(CVE-2024-300889)

漏洞原理 当内核将当前令牌对象的 _AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION 复制到用户模式时&#xff0c;错误位于函数 AuthzBasepCopyoutInternalSecurityAttributes 内部&#xff0c;该模式的结构如下&#xff1a; //0x30 bytes (sizeof) struct _AUTHZBASEP_SECURIT…

有哪些方法可以恢复ios15不小心删除的照片?

ios15怎么恢复删除的照片&#xff1f;在手机相册里意外删除了重要的照片&#xff1f;别担心&#xff01;本文将为你介绍如何在iOS 15系统中恢复已删除的照片。无需专业知识&#xff0c;只需要按照以下步骤操作&#xff0c;你就能轻松找回宝贵的回忆。 一、从iCloud云端恢复删除…

台式机通过网线直连笔记本,台式机通过笔记本上网【解决台式机没有网络的问题】

一、总览 将笔记本电脑和台式机使用网线连接起来。在笔记本电脑上打开网络和共享中心&#xff0c;进入“更改适配器设置”选项&#xff0c;找到当前连接的网卡&#xff0c;右键点击选择“属性”。在网卡属性中&#xff0c;找到“共享”选项卡&#xff0c;勾选“允许其他网络用…

怎么打印加密的Excel文件,有哪些方法?

很多小伙伴都喜欢使用Excel来创建或是编辑表格文档&#xff0c;因为Excel中的功能十分的丰富且强大&#xff0c;在Excel中我们可以对表格文档进行各种操作。有的小伙伴可能在打印时需要给每一页表格添加页码&#xff0c;但又不知道该在哪里找到相关操作&#xff0c;其实很简单&…

elasticsearch重置密码

0 案例背景 Elasticsearch三台集群环境&#xff0c;对外端口为6200&#xff0c;忘记elasticsearch密码&#xff0c;进行重置操作 注&#xff1a;若无特殊说明&#xff0c;三台服务器均需进行处理操作 1 停止es /rpa/bin/elasticsearch.sh stop 检查状态 ps -ef|grep elast…

武汉星起航:深度洞察消费趋势,亚马逊美国站选品独具匠心

亚马逊美国站作为全球电商巨头的重要分支&#xff0c;其选品特点不仅反映了美国市场的消费趋势&#xff0c;更引领着全球消费者的购物潮流。从运动户外、宠物用品到美容个人护理&#xff0c;亚马逊美国站的选品策略始终紧跟市场脉搏&#xff0c;为消费者提供丰富多样、品质优良…

深入浅出:npm常用命令详解与实战

theme: smartblue npm是什么 npm&#xff08;Node Package Manager&#xff09;是Node.js平台的默认包管理器&#xff0c;它让JavaScript开发者能够轻松地共享、管理和使用彼此编写的代码模块。npm不仅仅是一个安装工具&#xff0c;它还是一个全面的生态系统&#xff0c;用于发…

应用监控pinpoint调研

参考 https://blog.csdn.net/Rose_juvenile/article/details/135285508?utm_mediumdistribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0-135285508-blog-132330996.235^v43^pc_blog_bottom_relevance_base6&spm1001.2101.3001.4242.1&a…

2024年6月大众点评广州餐饮店铺POI分析20万家

2024年6月大众点评广州餐饮店铺POI共有199175家 店铺POI点位示例&#xff1a; 店铺id k9uiFADtAvs9EdPC 店铺名称 点都德(聚福楼店) 十分制服务评分 8.6 十分制环境评分 8.3 十分制划算评分 8.5 人均价格 77 评价数量 41673 店铺地址 惠福东路470号(富临食府对面) 大…

算法力扣刷题记录十【19.删除链表的倒数第N个节点】

前言 链表练习&#xff0c;继续 题目&#xff1a;力扣【19.删除链表的倒数第N个节点】 题目阅读 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1…

使用Anaconda Prompt安装cv2模块

问题 使用命令&#xff1a; conda install opencv-python出现找不到包 使用命令&#xff1a;pip install opencv-python下载很慢&#xff08;kb/s速度&#xff09;而且会由于断连报错&#xff08;不一定是网的问题&#xff09; 解决方案 使用清华镜像下载&#xff0c;这样…

逆变器使用手册:类型详解、安装要点与维护须知

逆变器随着可再生能源的兴起和移动电源需求的激增已成为连接直流电与交流电世界的桥梁&#xff0c;其重要性不言而喻。无论是太阳能发电系统的高效利用&#xff0c;还是汽车、游艇等移动设备的电力供应&#xff0c;逆变器都扮演着关键角色。然而&#xff0c;正确的使用方法是确…

3D在线展览馆的独特魅力,技术如何重塑展览业的未来?

在数字化和虚拟现实技术迅猛发展的今天&#xff0c;3D在线展览馆已经成为一种颇具前景的创新形式。搭建3D在线展览馆不仅能够突破传统展览的时空限制&#xff0c;还能为参观者提供身临其境的体验&#xff0c;极大地提升展示效果和用户互动。 一、3D在线展览馆的意义 1、突破时空…

idea 自动生成序列化数字

目标&#xff1a;当类继承Serializable后自动生成序列化Uid 网上查了很多说勾选class without ‘serialVersionUID’ 但是我勾选没用 最后发现&#xff0c;我勾选的是Serialization issues里面的配置&#xff0c;要勾选的是JVM languages下的 如下图所示&#xff0c;记录一下…

鸿蒙期末项目(2)

主界面 主界面和商店详情界面参考如下设计图&#xff08;灵感严重匮乏&#xff09; 简单起见&#xff0c;将整个app分为4个布局&#xff0c;分别是主界面、搜索界面、购物车界面&#xff0c;以及个人界面。 所以在app中也需要使用tab组件进行分割&#xff0c;且需要通过tabBa…

笔记本电脑录屏,教你3个方法,简单录屏

随着科技的飞速发展&#xff0c;笔记本电脑录屏功能已经不再局限于传统的录制需求&#xff0c;而是成为了探索屏幕动态的新方式。无论是创意工作者、游戏爱好者还是日常办公者&#xff0c;都可以借助这一功能&#xff0c;将屏幕上的精彩瞬间、重要信息或创新思路记录下来&#…