muduo网络库剖析——事件循环EventLoop类

muduo网络库剖析——事件循环EventLoop类

  • 前情
    • 从muduo到my_muduo
  • 概要
  • 框架与细节
    • 成员
    • 函数
    • 使用方法
  • 源码
  • 结尾

前情

从muduo到my_muduo

作为一个宏大的、功能健全的muduo库,考虑的肯定是众多情况是否可以高效满足;而作为学习者,我们需要抽取其中的精华进行简要实现,这要求我们足够了解muduo库。

做项目 = 模仿 + 修改,不要担心自己学了也不会写怎么办,重要的是积累,学到了这些方法,如果下次在遇到通用需求的时候你能够回想起之前的解决方法就够了。送上一段话!

在这里插入图片描述

概要

EventLoop这个类,是整个muduo的核心所在。分为mainLoop和subLoop。每一个Loop都是相互独立的,,有自己的事件循环,包括一个Poller监听者,Channel监听通道列表。mainLoop的逻辑除了对自己的事件循环做出相应的判断与行为之外,如果遇到新的连接建立,还会新申请一个subLoop。这个功能跟mainLoop中的acceptor有关,之后会讲到。整个Loop遵循一个重要原则叫做one loop one thread。意思是一个事件循环对应着一个线程。线程不能处理除自己对应的loop之外的其他loop。这种reactor优点是分开处理监听到的事件,能够将负载均衡,并且契合多处理器的特点,更大程度地接收并处理并发连接。相比于TinyWebserver那种reactor,少了很多线程安全处理上的花销,如请求队列的开锁解锁。那种reactor,所有的线程都从一个请求队列上获取连接请求,开锁与解锁等线程安全性操作花销太大。这种one loop one thread,将一个业务整理逻辑放在一个线程内,这样单个线程则不存在线程安全性花销,效率会更高。

EventLoop还用到了RAII机制,防止了指针的内存泄漏问题,将内存管理交给操作系统来完成。

框架与细节

成员

在这里插入图片描述
EventLoop中有一个Poller监听者和Channel通道列表。这里对应的变量是std::unique_ptr<Poller> poller_;ChannelList activeChannels_; 设置了一个threadId_对应one loop one thread原则。对于RAII机制,使用了智能指针。unique_ptr是独享指针,该指针指向的变量意味着不能够被拷贝构造,只能够移动构造,支持右值引用,将指针的所有权转移出去。atomic_bool是c++11提供的新特性,让该变量具有原子性,多线程访问是安全的。mutex也是用于多线程安全性的。callingPendingFunctors_是一个状态位,表示此时EventLoop正在处理一些函数回调。pendingFunctors_是函数回调队列。除此以外,某个thread运行某个loop时,如果loop锁定的thread不是现在的thread,就不符合one loop one thread。此时使用到wakeup唤醒线程的功能,单独用一个eventfd来监听线程唤醒事件,以及一个wakeupChannel通道。

函数

在这里插入图片描述
这是一个线程局部变量,意思是在变量的生命周期只在该线程中,是用来记录该线程是否已经有绑定的EventLoop了。前面一个__thread前缀来表示线程局部变量。

在这里插入图片描述
为唤醒线程创建一个套接字,eventfd支持NONBLOCK与CLOEXEC等宏定义。

在这里插入图片描述
创建一个EventLoop,需要判断这个thread是否已经有了对应的EventLoop,没有才会继续创建。还需要给wakeupChannel设定感兴趣的事件,并设定对应的回调函数。
在这里插入图片描述
loop循环中,包含poll监听,以及对监听到的channel通道列表中的channel进行handleEvent。对于其他的函数回调,还需要doPendingFunctors来处理对应的函数回调。

在这里插入图片描述
如果想要结束事件循环,也需要在自己的线程中退出事件循环。

在这里插入图片描述
对于处理某个事件,如果是在对应线程中,直接调用该事件回调就行了;如果是在其他线程中,就调用queueInLoop,将该事件回调放在回调队列中,等到一个事件循环最后执行doPendingFunctors来处理。

在这里插入图片描述
对于不在自己线程的函数回调,会将函数回调放在函数回调处理队列中。对于该loop不属于该thread以及在loop进行函数回调的过程中又来了新的回调,都会进行wakeup操作,通知对应thread进行处理。

在这里插入图片描述
对于EventLoop中的updateChannel和removeChannel方法,其实也是调用了其中poller的update与remove的方法。

在这里插入图片描述
对于wakeupFd,有读写功能,这样可以触发唤醒功能,能够让thread及时处理对应loop。

在这里插入图片描述
这段代码中执行了事件循环中的处理回调操作。用了一个局域unique_lock确保了functors的线程安全问题。这段代码之所以用一个vector functors来装pendingFunctors中的回调函数,而不是直接向下面这样:

void EventLoop::doPendingFunctors() {callingPendingFunctors_ = true;{std::unique_lock<std::mutex> lock(mutex_);for (const Functor &functor : pendingFunctors){functor(); // 执行当前loop需要执行的回调操作}}callingPendingFunctors_ = false;
}

有个好处就是一个线程对pendingFunctors的访问时间降低了很多,就一个swap的时间,等到有新的回调产生能够及时地正确地放入到pendingFunctors中。而向下面的代码,在执行回调的同时,占用着pendingFunctors的访问权限,这样的效率会远低于上面的代码。这也是muduo的精妙之处!

使用方法

源码

//EventLoop.h
#pragma once#include <functional>
#include <atomic>
#include <memory>
#include <semaphore.h>#include "Timestamp.h"
#include "noncopyable.h"
#include "Thread.h"class Poller;
class Channel;class EventLoop : noncopyable {
public:using Functor = std::function<void()>;  EventLoop();~EventLoop();void loop();void quit();void runInLoop(Functor cb);void queueInLoop(Functor cb);void updateChannel(Channel* channel);void removeChannel(Channel* channel);Timestamp getPollReturnTime() const { return pollReturnTime_; }void wakeup();bool hasChannel(Channel* channel);bool isInLoopThread() const { return threadId_ == CurrentThread::tid(); }
private:void handleRead();void doPendingFunctors();using ChannelList = std::vector<Channel*>;const pid_t threadId_;std::unique_ptr<Poller> poller_;Timestamp pollReturnTime_;std::atomic_bool looping_;std::atomic_bool quit_;int wakeupFd_;std::unique_ptr<Channel> wakeupChannel_;ChannelList activeChannels_;std::mutex mutex_;std::atomic_bool callingPendingFunctors_;std::vector<Functor> pendingFunctors_;
};
//EventLoop.cc
#include <sys/eventfd.h>
#include <mutex>
#include "EventLoop.h"
#include "Poller.h"
#include "Log.h"
#include "Channel.h"__thread EventLoop* t_loopInThisThread = nullptr; //线程中对应的EventLoopconst int kPollTimeMs = 10000; //poller的等待时间int createEventFd() {int evtfd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);if (evtfd < 0) {LOG_FATAL("%s--%s--%d--%d : eventfd error\n", __FILE__, __FUNCTION__, __LINE__, errno);}return evtfd;
}EventLoop::EventLoop() : threadId_(CurrentThread::tid()), poller_(Poller::newDefaultPoller(this)), looping_(false), quit_(false), callingPendingFunctors_(false), wakeupFd_(createEventFd()), wakeupChannel_(new Channel(this, wakeupFd_)) {LOG_INFO("%s--%s--%d : EventLoop created %p in thread %d\n", __FILE__, __FUNCTION__, __LINE__, this, threadId_);if (t_loopInThisThread) {LOG_FATAL("%s--%s--%d--%d : Another EventLoop %p exists in this thread %d\n", __FILE__, __FUNCTION__, __LINE__, errno, this, threadId_);}else t_loopInThisThread = this;wakeupChannel_->setReadCallback(std::bind(&EventLoop::handleRead, this));wakeupChannel_->enableReading();
}EventLoop::~EventLoop() {wakeupChannel_->disableAll();wakeupChannel_->remove();::close(wakeupFd_);t_loopInThisThread = nullptr;
}void EventLoop::loop() {looping_ = true;quit_ = false;LOG_INFO("%s--%s--%d : EventLoop %p start looping\n", __FILE__, __FUNCTION__, __LINE__, this);while (!quit_) {pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);for (Channel* channel : activeChannels_) {channel->handleEvent(pollReturnTime_);}doPendingFunctors();}LOG_INFO("%s--%s--%d : EventLoop %p stop looping\n", __FILE__, __FUNCTION__, __LINE__, this);looping_ = false;
}void EventLoop::quit() {quit_ = true;if (!isInLoopThread()) {wakeup();}
}void EventLoop::runInLoop(Functor cb) {if (isInLoopThread()) {cb();}else {queueInLoop(cb);}
}void EventLoop::queueInLoop(Functor cb) {{std::unique_lock<std::mutex> lock(mutex_);pendingFunctors_.push_back(cb);}if (!isInLoopThread() || callingPendingFunctors_) {wakeup();}
}void EventLoop::updateChannel(Channel* channel) {poller_->updateChannel(channel);
}void EventLoop::removeChannel(Channel* channel) {poller_->removeChannel(channel);
}void EventLoop::wakeup() {uint64_t one = 1;ssize_t n = ::write(wakeupFd_, &one, sizeof one);if (n != sizeof one) {LOG_ERROR("%s--%s--%d--%d : write error\n", __FILE__, __FUNCTION__, __LINE__, errno);}
}bool EventLoop::hasChannel(Channel* channel) {return poller_->hasChannel(channel);
}void EventLoop::handleRead() {uint64_t one = 1;ssize_t n = ::read(wakeupFd_, &one, sizeof one);if (n != sizeof one) {LOG_ERROR("%s--%s--%d--%d : read error\n", __FILE__, __FUNCTION__, __LINE__, errno);}
}
void EventLoop::doPendingFunctors() {std::vector<Functor> functors;callingPendingFunctors_ = true;{std::unique_lock<std::mutex> lock(mutex_);functors.swap(pendingFunctors_);}for (const Functor &functor : functors){functor(); // 执行当前loop需要执行的回调操作}callingPendingFunctors_ = false;
}

结尾

以上就是事件循环EventLoop类的相关介绍,以及我在进行项目重写的时候遇到的一些问题,和我自己的一些心得体会。发现写博客真的会记录好多你的成长,而且对于一个好的项目,写博客也是证明你确实有过深度思考,并且在之后面试或者工作时遇到同样的问题能够进行复盘的一种有效的手段。所以,希望uu们也可以像我一样,养成写博客的习惯,逐渐脱离菜鸡队列,向大佬前进!!!加油!!!

也希望我能够完成muduo网络库项目的深度学习与重写,并在功能上能够拓展。也希望在完成这个博客系列之后,能够引导想要学习muduo网络库源码的人,更好地探索这篇美丽繁华的土壤。致敬chenshuo大神!!!

鉴于博主只是一名平平无奇的大三学生,没什么项目经验,所以可能很多东西有所疏漏,如果有大神发现了,还劳烦您在评论区留言,我会努力尝试解决问题!

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

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

相关文章

鸿蒙开发系列教程(四)--ArkTS语言:基础知识

1、ArkTS语言介绍 ArkTS是HarmonyOS应用开发语言。它在保持TypeScript&#xff08;简称TS&#xff09;基本语法风格的基础上&#xff0c;对TS的动态类型特性施加更严格的约束&#xff0c;引入静态类型。同时&#xff0c;提供了声明式UI、状态管理等相应的能力&#xff0c;让开…

Mac book air 重新安装系统验证显示 untrusted_cert_title

环境&#xff1a; Mac Book Air macOS Sierra 问题描述&#xff1a; Mac book air 重新安装系统验证显示 untrusted_cert_title 解决方案&#xff1a; 1.终端输入命令行输入 date 会看到一个非常旧的日期 2.更改日期为当前时间 使用以下命令来设置日期和时间&#xff1a…

若依微服务框架,富文本加入图片保存时出现JSON parse error: Unexpected character (‘/‘ (code 47)):...

若依微服务框架&#xff0c;富文本加入图片保存时出现JSON parse error: Unexpected character 一、问题二、解决1.修改网关配置2、对数据进行加密解密2.1安装插件2.2vue页面加密使用2.3后台解密存储 一、问题 若依微服务项目在使用富文本框的时候&#xff0c;富文本加入图片进…

51单片机8*8点阵屏

8*8点阵屏 8*8点阵屏是一种LED显示屏&#xff0c;它由8行和8列的LED灯组成。每个LED灯的开闭状态都可以独立控制&#xff0c;从而可以显示出数字、字母、符号、图形等信息。 8*8点阵屏的原理是通过行列扫描的方式&#xff0c;控制LED灯的亮灭&#xff0c;从而显示出所需的图案或…

[flutter]GIF速度极快问题的两种解决方法

原因&#xff1a; 当GIF图没有设置播放间隔时间时&#xff0c;电脑上会默认间隔0.1s&#xff0c;而flutter默认0s。 解决方法一&#xff1a; 将图片改为webp格式。 解决方法二&#xff1a; 为图片设置帧频率&#xff0c;添加播放间隔。例如可以使用GIF依赖组件设置每秒运行…

【ARMv8M Cortex-M33 系列 7.2 -- HardFault 问题定位 1】

请阅读【嵌入式开发学习必备专栏 之 ARM Cortex-Mx专栏】 文章目录 问题背景堆栈对齐要求Cortex-M33 的 FPU 功能 问题背景 rt-thread 在PendSV_Handler退出的时候发生了HardFault_Handler是什么原因&#xff1f;且 LR 的值为0xfffffffd 堆栈对齐要求 在 ARM Cortex-M 架构中…

DNS寻址过程

用一张图详细的描述DNS寻址的过程&#xff0c;是高级前端进阶的网络篇&#xff1a; 主要是第三步要记仔细就行啦&#xff0c;每一步都要详细的记录下来&#xff0c;总结的脉络如下&#xff1a; 本地DNS缓存本地DNS服务器根域名服务器 顶级域名服务器再次顶级域名服务器权威域名…

【征服redis5】redis的Redisson客户端

目录 1 Redisson介绍 2. 与其他Java Redis客户端的比较 3.基本的配置与连接池 3.1 依赖和SDK 3.2 配置内容解析 4 实战案例&#xff1a;优雅的让Hash的某个Field过期 5 Redisson的强大功能 1 Redisson介绍 Redisson 最初由 GitHub 用户 “mrniko” 创建&#xff0c;并在…

高级架构师是如何设计一个系统的?

架构师如何设计系统&#xff1f; 系统拆分 通过DDD领域模型&#xff0c;对服务进行拆分&#xff0c;将一个系统拆分为多个子系统&#xff0c;做成SpringCloud的微服务。微服务设计时要尽可能做到少扇出&#xff0c;多扇入&#xff0c;根据服务器的承载&#xff0c;进行客户端负…

原码,补码的除法

目录 一.原码的除法 &#xff08;1&#xff09;恢复余数法 重点看这 &#xff08;2&#xff09;不恢复余数法&#xff08;加减交替法&#xff09; 重点看这 二. 补码除法运算 重点看这 我们已经学习了如何进行原码&#xff0c;补码的乘法&#xff1a; http://t.csdnimg…

进入docker容器,vi: command not found

问题描述&#xff1a; 进入docker容器&#xff0c;查看文件执行vim 命令&#xff0c;报错 vim: command not found。搜索解决方案&#xff0c;说执行一下 apt-get install vim命令&#xff0c;然后又报错 Unable to locate package vim。 解决&#xff1a; 1.执行 npt-get up…

二次开发在线预约上门服务、预约到家系统 增加开发票功能 轮播图链接跳转 uniapp代码

客户具体要求&#xff1a; 1、在我的个人中心里面增加一个 开票功能&#xff0c;点击进去之后可以查看到能开票的订单列表&#xff0c;如果是个人是填写姓名电话邮箱&#xff0c;就是填写单位名称 税号 邮箱&#xff0c;提交申请到后台审核&#xff0c;如果审核通过后线下人工…

鸿蒙开发环境配置-Windows

背景 入局鸿蒙开发&#xff0c;发现在 Windows 下面配置安装相关环境并没有像 Mac 一样简单&#xff0c;过程中遇到了一些问题记录一下。 Devceo Studio 下载安装 目前鸿蒙的 IDE 最新版是 4.0&#xff0c;通过这个连接可以下载&#xff0c;鸿蒙4.0下载连接。选择符合我们电…

1.C语言——基础知识

C语言基础知识 1.第一个C语言程序2.注释3.标识符4.关键字5.数据类型6.变量7.常量8.运算符9.输入输出输入输出 1.第一个C语言程序 C语言的编程框架 #include <stdio.h> int main() {/* 我的第一个 C 程序 */printf("Hello, World! \n");return 0; }2.注释 单行…

网络安全防护部署所需要注意的几点

顶层设计概念 考虑项目各层次和各要素&#xff0c;追根溯源&#xff0c;统揽全局&#xff0c;在最高层次上寻求问题的解决之道 顶层设计”不是自下而上的“摸着石头过河”&#xff0c;而是自上而下的“系统谋划” 网络安全分为 物理、网络、主机、应用、管理制度 边界最强 接…

微软Microsoft推出针对学生的AI练习英语口语工具”阅读教练“:Reading Coach

阅读教练官网链接&#xff1a;https://coach.microsoft.com AI工具专区&#xff1a;AI工具-喜好儿aigc 学生可以通过选择角色和设定&#xff0c;利用AI生成独特的故事&#xff0c;从而激发阅读兴趣并提高阅读流畅度。语音转文本AI能够实时分析学生的阅读流利性&#xff0c;检测…

Redis 面试题 | 01.精选Redis高频面试题

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

CSV文件中json列的处理2

如上所示&#xff0c;csv文件中包含以中括号{}包含的json字段&#xff0c;可用如下方法提取&#xff1a; import pandas as pd from datetime import date todaystr(date.today()) import jsonfilepath/Users/kangyongqing/Documents/kangyq/202401/调课功能使用统计/ file104…

了解Vue中日历插件Fullcalendar

实现效果如下图&#xff1a; 月视图 周视图 日视图 官方文档地址&#xff1a;Vue Component - Docs | FullCalendar 1、安装与FullCalendar相关的依赖项 npm install --save fullcalendar/vue fullcalendar/core fullcalendar/daygrid fullcalendar/timegrid fullcalend…

【设计模式】什么是外观模式并给出例子!

什么是外观模式&#xff1f; 外观模式是一种结构型设计模式&#xff0c;主要用于为复杂系统、库或框架提供一种简化的接口。这种模式通过定义一个包含单个方法的高级接口&#xff0c;来隐藏系统的复杂性&#xff0c;使得对外的API变得简洁并易于使用。 为什么要使用外观模式&a…