【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模块是网络编程框架中的核心组件,负责事件循环的驱动和管理。以下是对EventLoop模块的详细介绍: 作用与功能: EventLoop是网络服务器中负责循环的重要模块,它持续地监听、获取和处理各种事件,…...
ubuntu安装完桌面后如何启动
ubuntu安装完桌面后如何启动 在Ubuntu服务器上安装桌面环境后,您可以使用以下命令启动图形界面: sudo systemctl start gdm3如果您使用的是Ubuntu 20.04或更新版本,可能需要使用gdm3作为显示管理器。在早期的Ubuntu版本中,可能使…...
知识融合概述
文章目录 知识融合知识融合过程研究现状技术发展趋势 知识融合 知识融合的概念最早出现在1983年发表的文献中,并在20世纪九十年代得到研究者的广泛关注。而另一种知识融合的定义是指对来自多源的不同概念、上下文和不同表达等信息进行融合的过程认为知识融合的目标是…...
LIO-EKF: High Frequency LiDAR-Inertial Odometry using Extended Kalman Filters
一、论文摘要 里程计估计是每个需要在未知环境中导航的自主系统的关键要素。在现代移动机器人中,3D LiDAR 惯性系统通常用于执行此任务。通过融合 LiDAR 扫描和 IMU 测量,这些系统可以减少因顺序注册各个 LiDAR 扫描而引起的累积漂移,并提供稳…...
Shell脚本学习笔记(更新中...)
一、什么是shell shell的作用是: 解释执行用户输入的命令程序等。 用户输入一条命令,shell就解释一条。 键盘输入命令,LInux给与响应的方式,称之为交互式。 shell是一块包裹着系统核心的壳,处于操作系统的最外层&a…...
leetcode 210.课程表II
思路:拓补排序 其实就是对于第一个题的问题变了一个问法,上一个题本质上是求有没有环,这道题本质上就是让你求出来符合没有环的路径输出而已,本质上没有什么区别。 不同就在于这里需要你额外开一个数组用来存储你遍历这个有向图…...
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 xixi−0 有什么问题4. 延伸思考5. 参考文献 1. 问题 以方述诚老师课件中的案例为例: 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 头插,尾插 pop_front() pop_back 头删尾删 insert()函数 swap()函数 resize()函数 clear()函数 list类数…...
ELK 日志监控平台(一)- 快速搭建
文章目录 ELK 日志监控平台(一)- 快速搭建1.ELK 简介2.Elasticsearch安装部署3.Logstash安装部署4.Kibana安装部署5.日志收集DEMO5.1.创建SpringBoot应用依赖导入日志配置文件 logback.xml启动类目录结构启动项目 5.2.创建Logstash配置文件5.3.重新启动L…...
工作中写单片机代码,与学校里有什么不同?
来聊聊我的经历,提供几个提升方向,亲测有效,希望能让你少走几年弯路。 10几年前,还没参加工作的时候,主要是玩玩开发板,也接触不到实际产品的代码,很好奇那些产品级的代码是怎样的。 第一份工作…...
Unity LayerMask避坑笔记
今天使用Physics2D.OverlapAreaNonAlloc进行物理检测时候,通过LayerMask.NameToLayer传入了int值的LayerMask,结果一直识别不到,经过Debug才找到问题,竟是LayerMask的“值”传输有问题,记录一下。 直接贴代码输出结果&…...
(原创)从右到左排列RecycleView的数据
问题的提出 当我们写一个Recycleview时,默认的效果大概是这样的: 当然,我们也可以用表格布局管理器GridLayoutManager做成这样: 可以看到,默认的绘制方向是: 从左到右,从上到下 那么问题来了…...
【C语言】数据指针地址的取值、赋值、自增操作避坑
【C语言】数据指针的取值、赋值、自增操作避坑 文章目录 指针地址指针自增指针取值、赋值附录:压缩字符串、大小端格式转换压缩字符串浮点数压缩Packed-ASCII字符串 大小端转换什么是大端和小端数据传输中的大小端总结大小端转换函数 指针地址 请看下列代码&#…...
Java进阶-SpringCloud使用BeanUtil工具类简化对象之间的属性复制和操作
在Java编程中,BeanUtil工具类是一种强大且便捷的工具,用于简化对象之间的属性复制和操作。本文将介绍BeanUtil的基本功能,通过详细的代码示例展示其应用,并与其他类似工具进行对比。本文还将探讨BeanUtil在实际开发中的优势和使用…...
【ES6】ECMAS6新特性概览(一):变量声明let与const、箭头函数、模板字面量全面解析
🔥 个人主页:空白诗 🔥 热门专栏:【JavaScript】 文章目录 🌿 引言一、 let 和 const - 变量声明的新方式 🌟📌 var的问题回顾📌 let的革新📌 const的不变之美 二、 Arro…...
刷题之从前序遍历与中序遍历序列构造二叉树(leetcode)
从前序遍历与中序遍历序列构造二叉树 前序遍历:中左右 中序遍历:左中右 前序遍历的第一个数必定为根节点,再到中序遍历中找到该数,数的左边是左子树,右边是右子树,进行递归即可。 #include<vector>…...
微信小程序--微信开发者工具使用小技巧(3)
一、微信开发者工具使用小技巧 1、快速创建小程序页面 在app.json中的pages配置项,把需要创建的页面填写上去 2、快捷键使用 进入方式 1: 文件–>首选项–> keyboard shortcuts 进入快捷键查看与设置 进入方式 2: 设置–>快捷键…...
JDBC的 PreparedStatement 的用法和解释
文章目录 前言1、封装数据库连接和关闭操作数据库配置文件 config.properties 2、批量添加操作3、查询操作4、修改和删除操作总结 前言 PreparedStatement是预编译的,对于批量处理可以大大提高效率. 也叫JDBC存储过程 1、封装数据库连接和关闭操作 package org.springblade.m…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序
一、开发准备 环境搭建: 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 项目创建: File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...
html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...
Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...
STM32HAL库USART源代码解析及应用
STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...
aardio 自动识别验证码输入
技术尝试 上周在发学习日志时有网友提议“在网页上识别验证码”,于是尝试整合图像识别与网页自动化技术,完成了这套模拟登录流程。核心思路是:截图验证码→OCR识别→自动填充表单→提交并验证结果。 代码在这里 import soImage; import we…...
CSS3相关知识点
CSS3相关知识点 CSS3私有前缀私有前缀私有前缀存在的意义常见浏览器的私有前缀 CSS3基本语法CSS3 新增长度单位CSS3 新增颜色设置方式CSS3 新增选择器CSS3 新增盒模型相关属性box-sizing 怪异盒模型resize调整盒子大小box-shadow 盒子阴影opacity 不透明度 CSS3 新增背景属性ba…...
【Java多线程从青铜到王者】单例设计模式(八)
wait和sleep的区别 我们的wait也是提供了一个还有超时时间的版本,sleep也是可以指定时间的,也就是说时间一到就会解除阻塞,继续执行 wait和sleep都能被提前唤醒(虽然时间还没有到也可以提前唤醒),wait能被notify提前唤醒…...
