webserver服务器从零搭建到上线(九)|⭐️EventLoop类(一)——详解成员变量、简述成员方法

在本节中,我们一起来仔细探讨一下EpollPoller类。该类可以说是muduo库中最最核心的类了,一定要搞懂!

文章目录

  • 私有成员
    • `using ChannelList = std::vector<Channel*>`
    • `looping_`、`quit_`
    • `threadId_`
    • `pollReturnTime_`、`poller_`
    • `wakeup_fd`、`wakeupChannel_`
      • int wakeupFd_
      • std::unique_ptr<Channel> wakeupChannel_
    • ChannelList activeChannels_
    • 和回调相关的变量
  • 简介成员方法
  • 实现获取当前程序运行所属线程

私有成员

//事件循环类    主要包含了两个大模块channel Pollor(epoll的抽象)
class EventLoop : noncopyable {
public:using Functor = std::function<void()>;
private:using ChannelList = std::vector<Channel*>;std::atomic_bool looping_;  //原子操作,通过CAS实现std::atomic_bool quit_;     //标识退出loop循环const pid_t threadId_;      //记录当前loop所在线程的idTimestamp pollReturnTime_;  //poller返回事件的channels的时间点std::unique_ptr<Poller> poller_;int wakeupFd_; std::unique_ptr<Channel> wakeupChannel_;ChannelList activeChannels_;std::atomic_bool calllingPendingFunctors_; //标识当前loop是否有需要执行的回调操作std::vector<Functor> pendingFunctors_;  //存储loop需要执行的所有回调操作std::mutex mutex_; //互斥锁,用来保护上面vector容器的线程安全操作
};

从上到下依次讲解各个成员:

using ChannelList = std::vector<Channel*>

这个就不用说了,每个EventLoop下面都封装了很多很多的channel类,我们使用一个数组来对这些channel进行管理

looping_quit_

    std::atomic_bool looping_;  //原子操作,通过CAS实现std::atomic_bool quit_;     //标识退出loop循环

这两个变量都是跟事件循环本身是否继续工作下去相关的控制变量。

looping_用来判断当前的EventLoop是否已经退出,我们定义的是原子操作的布尔值,底层使用CAS实现。
quit_,一般我们是在其他线程来调用EventLoop的quit,来退出eventLoop循环

threadId_

const pid_t threadId_; //记录当前loop所在线程的id

这里我们需要结合图片来讲解:

首先需要明确的是,这里的Reactor就表示我们的EventLoop,并且每一个线程都应该有而且只有一个Reactor模型。

在多线程反应堆模型中,mainReactor只用来建立新用户的连接,拿到cfd之后,把该fd和它感兴趣的事件打包成一个channel,然后唤醒我们某一个workerReactor(使用轮询的方式),把这个channel扔给一个workerReactor,每一个 workReactor 都监听一组channel,并且每一组channel发生的事件都得在自己的EventLoop线程中去执行

所以在代码上就是通过这个threadId来实现这个流程,因为threadId作为EventLoop的成员变量就记录了创建的EventLoop对象所在的线程ID,等到运行时和当前工作的线程一比较,就能够判断EventLoop在不在它自己的线程中。

更加具体的描述请看后面具体的应用。

pollReturnTime_poller_

    Timestamp pollReturnTime_;  //poller返回事件的channels的时间点std::unique_ptr<Poller> poller_;
  • 这里pollReturnTime_记录的是poller返回发生时间的channels的时间点。
    EventLoop调用的是多路事件分发器也就是我们的Poller类,事件分发器epoll_wait开启循环后就监听事件发生,有事件发生后就会给Reactor返回发生事件的event,就是我们之前写的EPollPoler中Poll函数处理的那个activeChannels,其函数返回值就是 epoll_wait 监听到事件发生的时间戳。
  • 接下来当然少不了我们的poller了,这是Reactor模型中需要管理的重要资源多路事件分发器。

wakeup_fdwakeupChannel_

    int wakeupFd_; std::unique_ptr<Channel> wakeupChannel_;

这是两个相当重要的组建,我们首先介绍 wakeupFd_。

int wakeupFd_

我们想要弄明白muduo库,就必须**搞明白mainReactor如何给我们的subReactor分配新连接**,muduo库中使用的是轮询操作,那么它具体是如何唤醒subReactor线程的呢?要知道,在subReactor中如果没有事件发生,他们loop所在的线程都是阻塞的,假如说现在mainReactor监听到了一个新用户的连接,得到了表示新用户连接的fd\以及感兴趣事件的channel的话,他把这个channel怎么扔给subReactor呢?

假如说我通过轮询,决定把新用户连接的channel分发给下面的那个subReactor,我怎么把他叫醒呢?

这其实就是统一事件元的原理了,

在我们的libevent中,它的基本原理和muduo差不多,不过在唤醒子线程的步骤中,它采用的是socketpair。创建了一个本地socket的数组,跟管道不一样的是,这两个socket都是可读可写的,不像管道只能一端读、一端写。

我们的muduo采用的是系统调用eventfd,这个eventfd就是用于线程通信的,我这个线程可以通知其他线程起来做事,并且调用这个方法内核可以直接去notify用户空间的应用程序线程起来做事情,效率非常之高

所以这个wakeupFd_就是我们使用函数eventfd创建出来的,主要作用就是当mainLoop获取一个新用户的channel,通过轮询算法选择一个subLoop反应堆,通过该成员唤醒subloop。

std::unique_ptr wakeupChannel_

这个wakeupChannel肯定是要把wakeupFd封装起来的,因为我们在Poller里面并不会直接操作fd,而是在操作channel。

ChannelList activeChannels_

这个就是我们的EventLoop所管理的所有的Channel。并且他们都已经有相应的事件被激活了,该成员肯定是要被用于回调中的。

和回调相关的变量

    std::atomic_bool calllingPendingFunctors_; //标识当前loop是否有需要执行的回调操作std::vector<Functor> pendingFunctors_;  //存储loop需要执行的所有回调操作std::mutex mutex_; //互斥锁,用来保护上面vector容器的线程安全操作

可以看到,我们的EventLoop类还是比较复杂的,至于为什么需要这些成员变量后续会进行一个总结性的探讨。

简介成员方法

//事件循环类    主要包含了两个大模块channel Pollor(epoll的抽象)
class EventLoop : noncopyable {
public:using Functor = std::function<void()>;EventLoop();~EventLoop();//开启事件循环void loop();//退出事件循环void quit();Timestamp pollReturnTime() const { return pollReturnTime_; }// 在当前loop中执行cbvoid runInLoop(Functor cb);//把cb放入队列中,唤醒loop所在的线程后再去执行cbvoid queueInLoop(Functor cb);//用来唤醒loop所在的线程void wakeup();// EventLoop的方法==》Poller的方法void updateChannel(Channel *channel);void removeChannel(Channel *channel);void hasChannel(Channel *channel);//判断EventLoop对象是否已经在自己的线程里面bool isInLoopThread() const { return threadId_ == CurrentThread::tid(); }
private:void handleRead(); //wake up我们唤醒来使用的void doPendingFunctors(); //执行回调的内部接口

这里主要强调一下函数 isInLoopThread()

这里的CurrentThread::tid()是返回当前线程的线程ID;

如果这两个相等的话,说明我们的EventLoop对象目前所处的线程就在创建它的线程里,那么我们可以正常执行回调,如果不在的话,我们就得调用queueInLoop,当唤醒到它自己线程的时候,才去执行该loop相关的回调操作。

因为我们前文已经反复强调过,我们的每一个channel都有自己的EventLoop,每一个EventLoop也只属于一个线程,所以当事件发生需要执行回调,我们不能让别的EventLoop来执行,必须让自己的EventLoop来执行相对应的回调。

那么问题来了,为什么必须得是对应的EventLoop来处理回调任务呢?

  • 线程安全问题
    • EventLoop及其管理的资源(如Channel、Poller等)并不是线程安全的。如果一个EventLoop对象被多个线程同时访问,可能会导致数据竞争、状态不一致等问题,最终导致程序崩溃或产生难以调试的错误。
  • 事件处理顺序错乱
    • EventLoop依赖于事件循环机制按顺序处理事件。如果回调操作由非对应的EventLoop调用,事件处理的顺序可能会错乱,导致意外的行为。例如,某些依赖顺序的事件处理(如读取数据后处理数据)可能会发生在不正确的顺序,从而导致逻辑错误。

实现获取当前程序运行所属线程

//CurrenThread.h
#pragma once#include <unistd.h>
#include <syscall.h>namespace CurrentThread {extern __thread int t_cachedTid;void cacheTid();inline int tid() {if (__builtin_expect(t_cachedTid == 0, 0))cacheTid();return t_cachedTid;}
}//CurrenThread.cc
#include "CurrentThread.h"namespace CurrentThread {__thread int t_cachedTid = 0;void cacheTid() {if (t_cachedTid == 0) {//通过linux系统调用,获取当前线程的tid值t_cachedTid = static_cast<pid_t>(::syscall(SYS_gettid));}}
} // namespace CurrentThread

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

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

相关文章

一种基于单片机的智能饮水机设计

随着人们生活水平的提高&#xff0c;对美好生活质量的追求也越来越高。饮 水机是人们日常生活不可或缺的&#xff0c;实现饮水机的智能化控制不但方便&#xff0c; 而且更加安全。本文提出一种基于单片机的智能饮水控制系统&#xff0c;通过传 感器实现对水温的监测&#xff0c…

Redis(十四) 主从模式

文章目录 前言什么是分布式系统主从模式实现Redis主从模式主从模式原理nagle 算法拓扑结构主从模式实现的过程psync实时复制 前言 Redis 作为在内存中操作数据的服务器系统&#xff0c;每时都会接收成千上万的请求&#xff0c;如果我们的业务只在单个服务器上面部署了 Redis&a…

Android环境下Mesa初始化流程重学习之eglInitialize

Mesa初始化流程重学习之eglInitialize 引言 说来也惭愧&#xff0c;Mesa搞了这么久了&#xff0c;每次都想深入下&#xff0c;可是每次都是浅尝辄止了。这次趁着有了一定的闲暇时间并且有了调试景嘉微显卡的机会&#xff0c;还是想重新学习下&#xff0c;深入研究下&#xff0…

常见排序算法之插入排序

目录 一、直接插入排序 1.1 什么是插入排序 1.2 代码思路 1.3 C语言源码 二、希尔排序 2.0 插入排序的弊端 2.1 什么是希尔排序&#xff1f; 2.2 排序思路 2.3 C语言源码 一、直接插入排序 1.1 什么是插入排序 插入排序是一种简单直观的排序算法&#xff0c;它通过构…

LCD屏入门(基于ESP32)

主要参考资料&#xff1a; B站【乐鑫全球开发者大会】DevCon23 #17 &#xff5c;HMI 智能屏解决方案 目录 1.LCD屏幕硬件层2.LVGL驱动层 1.LCD屏幕硬件层 MCU常用的驱动接口在下面&#xff0c;大致可以划分为串口屏和并口屏。 串口屏相较于并行屏优势是占用IO少&#xff0c;相…

【oracle】Oracle RAC中的GNS到底是什么?

本文为云贝教育 刘峰 原创&#xff0c;请尊重知识产权&#xff0c;转发请注明出处&#xff0c;不接受任何抄袭、演绎和未经注明出处的转载 一、概述 Oracle Grid Naming Service (GNS) 是Oracle Grid Infrastructure的一个重要组件&#xff0c;它提供了一种集中式的命名服务&…

[机缘参悟-191] - 《道家-水木然人间清醒1》读书笔记 -14- 关系界限 - 经济和人格上的独立,走向成熟的必经之路,才能更好的谈其他情感(IT)

目录 前言&#xff1a; 1、“友善的孤独者” 2、“外向的孤独者” 3、道不同不相为谋 4、警惕依赖 5、完整独立的个体 6、不必纠正他人的错误&#xff0c;除非他影响了你 7、不再期待别人能理解自己&#xff0c;只有高维向下兼容你的人才能理解你 8、只有高维和同频的…

ChatGPT魔法,定制个性化提示词!

扮演Prompt创作者的角色 我想让你成为我的Prompt创作者。你的目标是帮助我创建最佳的Prompt&#xff0c;这个Prompt将由 你ChatGPT使用。 你将遵循以下过程&#xff1a; 1.首先&#xff0c;你会问我Prompt是关于什么的。我会告诉你&#xff0c;但我们需要通过不断的重复来改进…

jQuery下载教程

官网&#xff1a;https://jquery.com/ ** ** 点击为压缩版本 将网站打开 界面上邮件保存为js文件即可 在html文件中引入即可 <html> <head></head> <body><script src"./js/jquery-3.6.3.js"> </script> </body> <…

JDK9-21新特性概览(持续更新)

JDK9-21新特性概览 一、JDK9新特性1.1 JEP 102: Process API Updates简介案例风险 1.2 JEP 193: Variable Handles简介案例风险 1.3 JEP 200: The Modular JDK简介案例风险 1.4 JEP 213: Milling Project Coin简介案例1.1 try-with-resources增强1.2 接口中私有方法 1.5 JEP 22…

SpringBoot实现接口防抖的几种方案,杜绝重复提交

插&#xff1a; AI时代&#xff0c;程序员或多或少要了解些人工智能&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家(前言 – 人工智能教程 ) 坚持不懈&#xff0c;越努力越幸运&#xff0c;大家…

RK3568平台(camera篇)V4L2查询获取设置设备

一.查询设备能力VIDIOC_QUERYCAP struct v4l2_capability cap; ioctl(fd, VIDIOC_QUERYCAP, &cap) struct v4l2_capability 结构体描述了视频采集设备的 driver 信息。 struct v4l2_capability { __u8 driver[16]; // 驱动名字 __u8 card[32]; // 设备名字 __u8 bus_inf…

详解makefile中的foreach

在 Makefile 中&#xff0c;foreach 函数用于迭代处理一个以空格分隔的列表&#xff0c;并针对列表中的每个元素执行相同的操作。这个函数通常用于循环处理一组变量或文件名&#xff0c;并执行相同的规则或命令。 语法&#xff1a; makefile Copy Code $(foreach var, list, …

ThingsBoard物联网网关在智慧城市数据采集中的应用

智慧城市由监控中心、采集网关、前端采集设备、前端感应执行器组成。 为何选用ThingsBoard作为平台 监控中心为物联网平台&#xff0c;该平台包含云计算、大数据、人工智能、物联网、GIS、云安全等主要模块&#xff0c;具备数据采集、数据交换、超大规模计算、数据分析、数据应…

防火墙如何端口映射?

防火墙端口映射&#xff08;Firewall Port Mapping&#xff09;是一种网络技术&#xff0c;通过对防火墙配置进行调整&#xff0c;允许外部网络用户访问内部网络中的指定端口。该技术使得外部用户可以通过公共网络访问内部网络中的特定服务或应用程序&#xff0c;从而实现远程访…

JavaScript--作用域是什么

作用域是什么 编译原理 在传统的编译语言中&#xff0c;程序中的一段源代码在执行之前会经历三个步骤。成为编译 分词/词法分析 这个过程由字符组成的字符串分解成有意义的代码块&#xff0c;这些代码块成为词法单元。 分词和词法分析之间的主要差异在于词法单元的识别是有…

实战指南:Vue 2基座 + Vue 3 + Vite + TypeScript微前端架构实现动态菜单与登录共享

实战指南&#xff1a;Vue 2基座 Vue 3 Vite TypeScript子应用vue2微前端架构实现动态菜单与登录共享 导读&#xff1a; 在当今的前端开发中&#xff0c;微前端架构已经成为了一种流行的架构模式。本文将介绍如何结合Vue 2基座、Vue 3子应用、Vite构建工具和TypeScript语言…

基于Docker的ROS开发

本文主要介绍如何使用Docker在Windows和Linux环境中部署并使用ROS&#xff0c;通过Docker Container运行ROS&#xff0c;可以方便我们在一个本地环境中运行多个ROS版本。 更多内容&#xff0c;访问专栏目录获取实时更新。 关于ROS的版本 参考ROS1 Distribution Wiki和ROS2 Dis…

nginx源码阅读理解 [持续更新,建议关注]

文章目录 前述一、nginx 进程模型基本流程二、源码里的小点1.对字符串操作都进行了原生实现2.配置文件解析也是原生实现待续 前述 通过对 nginx 的了解和代码简单阅读&#xff0c;发现这个C代码的中间件确实存在过人之处&#xff0c;使用场景特别多&#xff0c;插件模块很丰富…

10款AI工具,让工作生活学习更高效

我看大家都推荐的差不多了&#xff0c;常见好用的PC软件就那些&#xff0c;我不想反复“咀嚼”了&#xff0c;我想另辟蹊径推荐点不一样的&#xff0c;比如10款PC端的AI网站。AI已经全方位“侵入”我们的生活&#xff0c;从AI写作到AI绘画&#xff0c;从AI视频到AI语音&#xf…