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

本文主要是介绍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

这篇关于webserver服务器从零搭建到上线(九)|⭐️EventLoop类(一)——详解成员变量、简述成员方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1012717

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

服务器集群同步时间手记

1.时间服务器配置(必须root用户) (1)检查ntp是否安装 [root@node1 桌面]# rpm -qa|grep ntpntp-4.2.6p5-10.el6.centos.x86_64fontpackages-filesystem-1.41-1.1.el6.noarchntpdate-4.2.6p5-10.el6.centos.x86_64 (2)修改ntp配置文件 [r

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

变量与命名

引言         在前两个课时中,我们已经了解了 Python 程序的基本结构,学习了如何正确地使用缩进来组织代码,并且知道了注释的重要性。现在我们将进一步深入到 Python 编程的核心——变量与命名。变量是我们存储数据的主要方式,而合理的命名则有助于提高代码的可读性和可维护性。 变量的概念与使用         在 Python 中,变量是一种用来存储数据值的标识符。创建变量很简单,

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

浅谈主机加固,六种有效的主机加固方法

在数字化时代,数据的价值不言而喻,但随之而来的安全威胁也日益严峻。从勒索病毒到内部泄露,企业的数据安全面临着前所未有的挑战。为了应对这些挑战,一种全新的主机加固解决方案应运而生。 MCK主机加固解决方案,采用先进的安全容器中间件技术,构建起一套内核级的纵深立体防护体系。这一体系突破了传统安全防护的局限,即使在管理员权限被恶意利用的情况下,也能确保服务器的安全稳定运行。 普适主机加固措施:

webm怎么转换成mp4?这几种方法超多人在用!

webm怎么转换成mp4?WebM作为一种新兴的视频编码格式,近年来逐渐进入大众视野,其背后承载着诸多优势,但同时也伴随着不容忽视的局限性,首要挑战在于其兼容性边界,尽管WebM已广泛适应于众多网站与软件平台,但在特定应用环境或老旧设备上,其兼容难题依旧凸显,为用户体验带来不便,再者,WebM格式的非普适性也体现在编辑流程上,由于它并非行业内的通用标准,编辑过程中可能会遭遇格式不兼容的障碍,导致操

搭建Kafka+zookeeper集群调度

前言 硬件环境 172.18.0.5        kafkazk1        Kafka+zookeeper                Kafka Broker集群 172.18.0.6        kafkazk2        Kafka+zookeeper                Kafka Broker集群 172.18.0.7        kafkazk3

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)