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

【Muduo】三大核心之EventLoop

Muduo网络库的EventLoop模块是网络编程框架中的核心组件,负责事件循环的驱动和管理。以下是对EventLoop模块的详细介绍:

作用与功能

  • EventLoop是网络服务器中负责循环的重要模块,它持续地监听、获取和处理各种事件,如IO事件、定时器事件等。
  • 它通过轮询访问Poller(如EPollPoller),获取激活的Channel列表,然后使Channel根据自身情况调用相应的回调函数来处理事件。
  • EventLoop确保了每个Loop都是相互独立的,拥有自己的事件循环、Poller监听者和Channel监听通道列表。

与Poller的关系

  • Poller负责从事件监听器上获取监听结果,即哪些文件描述符(fd)上发生了哪些事件。
  • EventLoop会轮询访问Poller,以获取这些发生事件的fd及其相关事件。

与Channel的关系

  • Channel类是对文件描述符(fd)以及其相关事件的封装。它保存了fd的感兴趣事件、实际发生的事件以及每种事件对应的处理函数。
  • 当Poller检测到某个fd上有事件发生时,EventLoop会找到对应的Channel,并调用其上的回调函数来处理该事件。

线程模型

  • EventLoop遵循“one loop one thread”的原则,即每个EventLoop都在一个独立的线程上运行。
  • 这种设计使得事件处理更加高效和清晰,避免了多线程环境下的竞态条件和同步问题。

mainLoop和subLoop

在Muduo网络库中,mainLoop和subLoop都是EventLoop的实例,它们分别代表主事件循环和子事件循环。

mainLoop(主事件循环)

  • mainLoop是整个Muduo网络库的核心事件循环。它负责监听服务器套接字(通常是listenfd),并接受来自客户端的连接请求。
  • mainLoop运行一个Accrptor,包含一个Poller,用于监听一个特定的非阻塞的服务器sockfd上的读事件。当Poller检测到有读事件发生时(一般是新用户连接),mainLoop会在线程池中通过轮询算法选择一个subLoop来处理这个连接的读写和关闭事件。Acceptor将在后续阐述。
  • mainLoop遵循 “one loop one thread” 的原则,即每个mainLoop都在一个独立的线程上运行。这确保了事件处理的高效性和清晰性,避免了多线程环境下的竞态条件和同步问题。

subLoop(子事件循环)

  • subLoop是mainLoop的子事件循环,用于处理已建立的连接的读写和关闭事件。每个subLoop都在一个独立的线程上运行,有一个用于唤醒自身的fd和Channel,运行一个Poller,并保存自己管理的多个Channel,以实现并发处理多个连接的目的。
  • 当mainLoop接受到一个新的连接请求时,它会根据EventLoopThreadPool中的线程来选择一个subLoop,将新创建的TcpConnection的Channel放入这个subLoop中。这个subLoop会接管该连接的fd,并监听其上的读写和关闭事件。
  • subLoop中的事件处理逻辑与mainLoop类似,也是通过Poller来监听fd上的事件,并调用相应的回调函数来处理这些事件。
  • 由于subLoop是独立的线程,因此它们可以并行处理多个连接,从而提高了服务器的并发处理能力。

总的来说,mainLoop和subLoop共同构成了Muduo网络库的事件驱动编程框架。mainLoop负责监听服务器套接字并接受连接请求,而subLoop则负责处理已建立的连接的读写和关闭事件。通过合理的线程调度和事件处理机制,Muduo网络库能够高效、稳定地处理大量的并发连接请求。

EventLoop.h

#pragma once
#include "noncopyable.h"
#include "Timestamp.h"
#include "CurrentThread.h"
#include "LogStream.h"#include <functional>
#include <vector>
#include <atomic>
#include <memory>
#include <mutex>
#include <sys/types.h>class Channel;
class Poller;/*** 事件循环类  两大模型:Channel  Poller* mainLoop只负责处理IO,并返回client的fd* subLoop负责监听poll,并处理相应的回调* 两者之间通过weakupfd进行通信
*/
class EventLoop : noncopyable
{
public:using Functor = std::function<void()>;EventLoop();~EventLoop();// 开启loopvoid loop();// 退出loopvoid quit();Timestamp pollReturnTime() const { return pollReturnTime_; }// 在当前loop执行cbvoid runInLoop(Functor cb);// 把cb放入队列,唤醒subloop所在的线程,执行cbvoid queueInLoop(Functor cb);size_t queueSize() const;// 唤醒loop所在的线程,EventLoop::queueInLoop中调用void wakeup();// EventLoop方法 =》 Poller的方法void updateChannel(Channel *channel);void removeChannel(Channel *channel);bool hasChannel(Channel *channel);// 判断EventLoop对象是否在自己的线程中bool isInLoopThread() const {return threadId_ == CurrentThread::tid();}private:// waked up后的一个操作 void handleRead();       // 执行回调void doPendingFunctors(); 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_; // 当mainLoop获取一个新用户的channel,通过轮询算法选择一个subloop,通过该成员唤醒subloop处理channel。使用eventfd// unlike in TimerQueue, which is an internal class,// we don't expose Channel to client.std::unique_ptr<Channel> wakeupChannel_;// scratch variablesChannelList activeChannels_;std::atomic_bool callingPendingFunctors_; // 标识当前loop是否有需要执行的回调操作,正在执行则为truestd::vector<Functor> pendingFunctors_;    // 存储loop需要执行的所有回调操作std::mutex mutex_;                        // 保护pendingFunctors_线程安全
};

EventLoop.cc

#include "EventLoop.h"
#include "LogStream.h"
#include "Poller.h"
#include "Channel.h"#include <sys/eventfd.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <iostream>// 防止一个线程创建多个EventLoop    threadLocal
__thread EventLoop *t_loopInThisThread = nullptr;// 定义Poller超时时间
const int kPollTimeMs = 10000;// 创建weakupfd,用来notify唤醒subReactor处理新来的channel
int createEventfd()
{int evtfd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);if (evtfd < 0){LOG_FATAL << "Failed in eventfd" << errno;}return evtfd;
}EventLoop::EventLoop(): looping_(false),quit_(false),callingPendingFunctors_(false),threadId_(CurrentThread::tid()),poller_(Poller::newDefaultPoller(this)),wakeupFd_(createEventfd()),wakeupChannel_(new Channel(this, wakeupFd_))
{LOG_DEBUG << "EventLoop created " << this << " in thread " << threadId_;if (t_loopInThisThread){LOG_FATAL << "Another EventLoop " << t_loopInThisThread<< " exists in this thread " << threadId_;}else{t_loopInThisThread = this;}// 设置weakupfd的事件类型以及发生事件后的回调操作wakeupChannel_->setReadCallback(std::bind(&EventLoop::handleRead, this));// we are always reading the wakeupfd// 每一个EventLoop都将监听weakupChannel的EPOLLIN读事件了// 作用是subloop在阻塞时能够被mainLoop通过weakupfd唤醒wakeupChannel_->enableReading();
}EventLoop::~EventLoop()
{LOG_DEBUG << "EventLoop " << this << " of thread " << threadId_<< " destructs in thread " << CurrentThread::tid();wakeupChannel_->disableAll();wakeupChannel_->remove();::close(wakeupFd_);t_loopInThisThread = NULL;
}void EventLoop::handleRead()
{uint64_t one = 1;ssize_t n = read(wakeupFd_, &one, sizeof one);if (n != sizeof one){LOG_ERROR << "EventLoop::handleRead() reads " << n << " bytes instead of 8";}
}void EventLoop::loop()
{looping_ = true;quit_ = false;LOG_INFO << "EventLoop " << this << " start looping";while (!quit_){activeChannels_.clear();// 当前EventLoop的Poll,监听两类fd,client的fd(正常通信的,在baseloop中)和 weakupfd(mainLoop 和 subLoop 通信用来唤醒sub的)pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);for (Channel *channel : activeChannels_){// Poller监听哪些channel发生事件了,然后上报给EventLoop,通知channel处理相应的事件channel->handleEvent(pollReturnTime_);}// 执行当前EventLoop事件循环需要处理的回调操作/*** IO线程 mainLoop 只 accept 然后返回client通信用的fd <= 用channel打包 并分发给 subloop* mainLoop事先注册一个回调cb(需要subLoop来执行),weakup subloop后,* 执行下面的方法,执行之前mainLoop注册的cb操作(一个或多个)*/doPendingFunctors();}LOG_INFO << "EventLoop " << this << " stop looping";looping_ = false;
}/*** 退出事件循环* 1、loop在自己的线程中 调用quit,此时肯定没有阻塞在poll中* 2、在其他线程中调用quit,如在subloop(woker)中调用mainLoop(IO)的qiut**                  mainLoop* *      Muduo库没有 生产者-消费者线程安全的队列 存储Channel*      直接使用wakeupfd进行线程间的唤醒       ** subLoop1         subLoop2        subLoop3*/
void EventLoop::quit()
{quit_ = true;// 2中,此时,若当前woker线程不等于mainLoop线程,将本线程在poll中唤醒if (!isInLoopThread()){wakeup();}
}void EventLoop::runInLoop(Functor cb)
{// LOG_DEBUG<<"EventLoop::runInLoop  cb:" << (cb != 0);if (isInLoopThread()) // 产生段错误{ // 在当前loop线程中 执行cbLOG_DEBUG << "在当前loop线程中 执行cb";cb();}else{ // 在其他loop线程执行cb,需要唤醒其loop所在线程,执行cbLOG_DEBUG << "在其他loop线程执行cb,需要唤醒其loop所在线程,执行cb";queueInLoop(cb);}
}void EventLoop::queueInLoop(Functor cb)
{{std::unique_lock<std::mutex> ulock(mutex_);pendingFunctors_.emplace_back(cb);}// 唤醒相应的,需要执行上面回调操作的loop线程// 若当前线程正在执行回调doPendingFunctors,但是又有了新的回调cb// 防止执行完回调后又阻塞在poll上无法执行新cb,所以预先wakeup写入一个数据if (!isInLoopThread() || callingPendingFunctors_) {wakeup(); // 唤醒loop所在线程}
}// 用来唤醒loop所在的线程,向wakeupfd写一个数据,wakeupChannel就发生读事件,当前loop线程就会被唤醒
void EventLoop::wakeup()
{uint64_t one = 1;ssize_t n = ::write(wakeupFd_, &one, sizeof one);if (n != sizeof one){LOG_ERROR << "EventLoop::wakeup() writes " << n << " bytes instead of 8";}
}void EventLoop::updateChannel(Channel *channel)
{// channel是发起方,通过loop调用pollpoller_->updateChannel(channel);
}void EventLoop::removeChannel(Channel *channel)
{// channel是发起方,通过loop调用pollpoller_->removeChannel(channel);
}bool EventLoop::hasChannel(Channel *channel)
{return poller_->hasChannel(channel);
}// 执行回调,由TcpServer提供的回调函数
void EventLoop::doPendingFunctors()
{std::vector<Functor> functors;callingPendingFunctors_ = true; // 正在执行回调操作{ // 使用swap,将原pendingFunctors_置空并且释放,其他线程不会因为pendingFunctors_阻塞std::unique_lock<std::mutex> lock(mutex_);functors.swap(pendingFunctors_);}for (const Functor &functor : functors){functor(); // 执行当前loop需要的回调操作}callingPendingFunctors_ = false;
}

相关文章:

【Muduo】三大核心之EventLoop

Muduo网络库的EventLoop模块是网络编程框架中的核心组件&#xff0c;负责事件循环的驱动和管理。以下是对EventLoop模块的详细介绍&#xff1a; 作用与功能&#xff1a; EventLoop是网络服务器中负责循环的重要模块&#xff0c;它持续地监听、获取和处理各种事件&#xff0c;…...

ubuntu安装完桌面后如何启动

ubuntu安装完桌面后如何启动 在Ubuntu服务器上安装桌面环境后&#xff0c;您可以使用以下命令启动图形界面&#xff1a; sudo systemctl start gdm3如果您使用的是Ubuntu 20.04或更新版本&#xff0c;可能需要使用gdm3作为显示管理器。在早期的Ubuntu版本中&#xff0c;可能使…...

知识融合概述

文章目录 知识融合知识融合过程研究现状技术发展趋势 知识融合 知识融合的概念最早出现在1983年发表的文献中&#xff0c;并在20世纪九十年代得到研究者的广泛关注。而另一种知识融合的定义是指对来自多源的不同概念、上下文和不同表达等信息进行融合的过程认为知识融合的目标是…...

LIO-EKF: High Frequency LiDAR-Inertial Odometry using Extended Kalman Filters

一、论文摘要 里程计估计是每个需要在未知环境中导航的自主系统的关键要素。在现代移动机器人中&#xff0c;3D LiDAR 惯性系统通常用于执行此任务。通过融合 LiDAR 扫描和 IMU 测量&#xff0c;这些系统可以减少因顺序注册各个 LiDAR 扫描而引起的累积漂移&#xff0c;并提供稳…...

Shell脚本学习笔记(更新中...)

一、什么是shell shell的作用是&#xff1a; 解释执行用户输入的命令程序等。 用户输入一条命令&#xff0c;shell就解释一条。 键盘输入命令&#xff0c;LInux给与响应的方式&#xff0c;称之为交互式。 shell是一块包裹着系统核心的壳&#xff0c;处于操作系统的最外层&a…...

leetcode 210.课程表II

思路&#xff1a;拓补排序 其实就是对于第一个题的问题变了一个问法&#xff0c;上一个题本质上是求有没有环&#xff0c;这道题本质上就是让你求出来符合没有环的路径输出而已&#xff0c;本质上没有什么区别。 不同就在于这里需要你额外开一个数组用来存储你遍历这个有向图…...

SpringBootTest测试框架五

示例 package com.xxx;import com.xxx.ut.AbstractBasicTest; import com.xxx.ut.uttool.TestModel; import...

赛事|基于SprinBoot+vue的CSGO赛事管理系统(源码+数据库+文档)

CSGO赛事管理系统 目录 基于SprinBootvue的CSGO赛事管理系统 一、前言 二、系统设计 三、系统功能设计 1系统功能模块 2管理员功能模块 3参赛战队功能模块 4合作方功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&…...

线性化技巧:绝对值变量的线性化

文章目录 1. 问题2. 线性化3. 缺少 x i x i − 0 x_i^ \times x_i^- 0 xi​xi−​0 有什么问题4. 延伸思考5. 参考文献 1. 问题 以方述诚老师课件中的案例为例&#xff1a; m a x 3 x 1 − 2 x 2 − 4 ∣ x 3 ∣ s . t . − x 1 2 x 2 ≤ − 5 3 x 2 − x 3 ≥ 6 2 x 1 …...

List基本使用(C++)

目录 1.list的介绍 2.list的使用 list的构造 list的size() 和 max_size() list遍历操作 list元素修改操作 assign()函数 push_front(),push_back 头插&#xff0c;尾插 pop_front() pop_back 头删尾删 insert()函数 swap()函数 resize()函数 clear()函数 list类数…...

ELK 日志监控平台(一)- 快速搭建

文章目录 ELK 日志监控平台&#xff08;一&#xff09;- 快速搭建1.ELK 简介2.Elasticsearch安装部署3.Logstash安装部署4.Kibana安装部署5.日志收集DEMO5.1.创建SpringBoot应用依赖导入日志配置文件 logback.xml启动类目录结构启动项目 5.2.创建Logstash配置文件5.3.重新启动L…...

工作中写单片机代码,与学校里有什么不同?

来聊聊我的经历&#xff0c;提供几个提升方向&#xff0c;亲测有效&#xff0c;希望能让你少走几年弯路。 10几年前&#xff0c;还没参加工作的时候&#xff0c;主要是玩玩开发板&#xff0c;也接触不到实际产品的代码&#xff0c;很好奇那些产品级的代码是怎样的。 第一份工作…...

Unity LayerMask避坑笔记

今天使用Physics2D.OverlapAreaNonAlloc进行物理检测时候&#xff0c;通过LayerMask.NameToLayer传入了int值的LayerMask&#xff0c;结果一直识别不到&#xff0c;经过Debug才找到问题&#xff0c;竟是LayerMask的“值”传输有问题&#xff0c;记录一下。 直接贴代码输出结果&…...

(原创)从右到左排列RecycleView的数据

问题的提出 当我们写一个Recycleview时&#xff0c;默认的效果大概是这样的&#xff1a; 当然&#xff0c;我们也可以用表格布局管理器GridLayoutManager做成这样&#xff1a; 可以看到&#xff0c;默认的绘制方向是&#xff1a; 从左到右&#xff0c;从上到下 那么问题来了…...

【C语言】数据指针地址的取值、赋值、自增操作避坑

【C语言】数据指针的取值、赋值、自增操作避坑 文章目录 指针地址指针自增指针取值、赋值附录&#xff1a;压缩字符串、大小端格式转换压缩字符串浮点数压缩Packed-ASCII字符串 大小端转换什么是大端和小端数据传输中的大小端总结大小端转换函数 指针地址 请看下列代码&#…...

Java进阶-SpringCloud使用BeanUtil工具类简化对象之间的属性复制和操作

在Java编程中&#xff0c;BeanUtil工具类是一种强大且便捷的工具&#xff0c;用于简化对象之间的属性复制和操作。本文将介绍BeanUtil的基本功能&#xff0c;通过详细的代码示例展示其应用&#xff0c;并与其他类似工具进行对比。本文还将探讨BeanUtil在实际开发中的优势和使用…...

【ES6】ECMAS6新特性概览(一):变量声明let与const、箭头函数、模板字面量全面解析

&#x1f525; 个人主页&#xff1a;空白诗 &#x1f525; 热门专栏&#xff1a;【JavaScript】 文章目录 &#x1f33f; 引言一、 let 和 const - 变量声明的新方式 &#x1f31f;&#x1f4cc; var的问题回顾&#x1f4cc; let的革新&#x1f4cc; const的不变之美 二、 Arro…...

刷题之从前序遍历与中序遍历序列构造二叉树(leetcode)

从前序遍历与中序遍历序列构造二叉树 前序遍历&#xff1a;中左右 中序遍历&#xff1a;左中右 前序遍历的第一个数必定为根节点&#xff0c;再到中序遍历中找到该数&#xff0c;数的左边是左子树&#xff0c;右边是右子树&#xff0c;进行递归即可。 #include<vector>…...

微信小程序--微信开发者工具使用小技巧(3)

一、微信开发者工具使用小技巧 1、快速创建小程序页面 在app.json中的pages配置项&#xff0c;把需要创建的页面填写上去 2、快捷键使用 进入方式 1&#xff1a; 文件–>首选项–> keyboard shortcuts 进入快捷键查看与设置 进入方式 2&#xff1a; 设置–>快捷键…...

JDBC的 PreparedStatement 的用法和解释

文章目录 前言1、封装数据库连接和关闭操作数据库配置文件 config.properties 2、批量添加操作3、查询操作4、修改和删除操作总结 前言 PreparedStatement是预编译的,对于批量处理可以大大提高效率. 也叫JDBC存储过程 1、封装数据库连接和关闭操作 package org.springblade.m…...

浅谈 React Hooks

React Hooks 是 React 16.8 引入的一组 API&#xff0c;用于在函数组件中使用 state 和其他 React 特性&#xff08;例如生命周期方法、context 等&#xff09;。Hooks 通过简洁的函数接口&#xff0c;解决了状态与 UI 的高度解耦&#xff0c;通过函数式编程范式实现更灵活 Rea…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)

CSI-2 协议详细解析 (一&#xff09; 1. CSI-2层定义&#xff08;CSI-2 Layer Definitions&#xff09; 分层结构 &#xff1a;CSI-2协议分为6层&#xff1a; 物理层&#xff08;PHY Layer&#xff09; &#xff1a; 定义电气特性、时钟机制和传输介质&#xff08;导线&#…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

今日科技热点速览

&#x1f525; 今日科技热点速览 &#x1f3ae; 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售&#xff0c;主打更强图形性能与沉浸式体验&#xff0c;支持多模态交互&#xff0c;受到全球玩家热捧 。 &#x1f916; 人工智能持续突破 DeepSeek-R1&…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

Spring AI与Spring Modulith核心技术解析

Spring AI核心架构解析 Spring AI&#xff08;https://spring.io/projects/spring-ai&#xff09;作为Spring生态中的AI集成框架&#xff0c;其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似&#xff0c;但特别为多语…...

Linux 中如何提取压缩文件 ?

Linux 是一种流行的开源操作系统&#xff0c;它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间&#xff0c;使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的&#xff0c;要在 …...

Vue ③-生命周期 || 脚手架

生命周期 思考&#xff1a;什么时候可以发送初始化渲染请求&#xff1f;&#xff08;越早越好&#xff09; 什么时候可以开始操作dom&#xff1f;&#xff08;至少dom得渲染出来&#xff09; Vue生命周期&#xff1a; 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...

沙箱虚拟化技术虚拟机容器之间的关系详解

问题 沙箱、虚拟化、容器三者分开一一介绍的话我知道他们各自都是什么东西&#xff0c;但是如果把三者放在一起&#xff0c;它们之间到底什么关系&#xff1f;又有什么联系呢&#xff1f;我不是很明白&#xff01;&#xff01;&#xff01; 就比如说&#xff1a; 沙箱&#…...