[muduo网络库]——muduo库三大核心组件之EventLoop类(剖析muduo网络库核心部分、设计思想)

接着上一节[muduo网络库]——muduo库三大核心组件之 Poller/EpollPoller类(剖析muduo网络库核心部分、设计思想),我们来剖析muduo库中最后一类核心组件,EventLoop类。
先回顾一下三大核心组件之间的关系。
在这里插入图片描述接着我们进入正题。

EventLoop

Poller封装了和事件监听有关的方法和成员,调用Poller派生类EpollPoller::poll方法,我们就可以获得发生事件的fd 及其 发生的事件。EventLoop是网络服务器中负责 循环 的重要模块,从而做到持续监听、持续获取监听结果、持续处理监听结果对应的事件。
也就是说: EventLoop起到一个驱动循环的功能,Poller负责从事件监听器上获取监听结果,Channel类将fd及其相关属性封装,并将fd及其感兴趣事件和发生的事件以及不同事件对应的回调函数封装在一起,这样在各个模块中传递更加方便。接着被EventLoop调用。
可能上面我画的图不能充分表达三者在muduo库中的角色,下面借用我在地铁站里吃闸机博主的图,可能会让大家看的更加直观。
在这里插入图片描述
在EventLoop就能够充分提现muduo库的重要思想:One Loop Per Thread
在muduo库里边有两种线程:一种里边的事件循环专门处理新用户连接(mainLoop( 也就是baseLoop)),一种里边的事件循环专门处理对应连接的所有读写事件(ioLoop)。

重要成员变量

std::unique_ptr<Poller> poller_;const pid_t threadId_; //记录当前loop所在线程的idTimeStamp pollReturnTime_; //poller返回发生事件的channels的时间点int wakeupFd_;
std::unique_ptr<Channel> wakeupChannel_;ChannelList activeChannels_;std::atomic_bool callingPendingFunctors_; std::vector<Functor> pendingFunctors_;std::mutex mutex_; 
  • poller_就不用在多说什么了,通过它会返回给EventLoop发生的事件。
  • wakeupFd_是非常重要的一个成员,与之对应的wakeupChannel_,起到了一个唤醒loop所在的线程的作用,因为当前线程主要阻塞在poll函数上,唤醒的方法时手动激活这个wakeupChannel_, 写入几个字节让Channel变为可读, 当然这个Channel也注册到Pooll中,在下面的成员函数会详细介绍它的实现。
  • threadId_创建时要保存当前时间循环所在的线程,用于之后运行时判断使用EventLoop的线程是否时EventLoop所属的线程.
  • pollReturnTime_保存poll返回的时间,用于计算从激活到调用回调函数的延迟
  • activeChannels_就是poller返回的所有发生事件的channel列表。
  • callingPendingFunctors_标识当前loop是否有需要执行的回调操作
  • pendingFunctors_存储loop需要执行的所有回调操作,避免本来属于当前线程的回调函数被其他线程调用,应该把这个回调函数添加到属于它所属的线程,等待它属于的线程被唤醒后调用,满足线程安全
  • mutex_互斥锁,用来保护vector容器的线程安全操作

重要成员函数

  • 最最最最重要的莫过于loop()
void EventLoop::loop()
{looping_ = true;quit_ = false;LOG_INFO("EventLoop %p start looping \n",this);while(!quit_){activeChannels_.clear();//监听两类fd 一种是client的fd  一种是wakeuppollReturnTime_ = poller_->poll(kPollTimeMs,&activeChannels_);for(Channel *channel : activeChannels_){//poller监听哪些channel发生事件了,然后上报给EventLoop,通知channel处理相应的事件channel->handleEvent(pollReturnTime_);}//执行当前EventLoop事件循环需要处理的回调操作/*** IO线程 mainloop accept fd <= channel  subloop* mainloop事先注册一个回调cb,需要subloop执行  * wakeup subloop后执行下面的方法 执行之前mainloop注册的cb回调* */doPendingFunctors();}LOG_INFO("EventLoop %p stop looping,\n",this);looping_ = false;
}

从代码中,我们可以看出最核心的部分就是调用了Poller的poll方法,它返回了发生的事件channel列表以及发生的时间now
接着可以看出还有一个doPendingFunctors函数

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;
} 

实际上,这个函数就是用来执行回调的,值得注意的一点就是: 这里使用了一个比较巧妙的思想就是,使用一个局部的vectorpendingFunctors_的交换,这样就避免了因为要读取这个pendingFunctors_的时候,没有释放锁,而新的事件往里写得时候写不进去(mainloop向subloop里面写回调)。

还有一点,一开始的时候很疑惑functor();是在执行什么呢?其实在这里我们可以看出来,经过交换functor();拿到的实际上pendingFunctors_.emplace_back(cb);中的内容,执行回调。那么pendingFunctors_怎么来的?

  • 那就是runInLoop以及queueInLoop
//在当前loop中执行cb
void EventLoop::runInLoop(Functor cb)
{if(isInLoopThread())//在当前的loop线程中,执行cb{cb();}else //在非当前loop执行cb,就需要唤醒loop所在线程执行cb{queueInLoop(cb);}}void EventLoop::queueInLoop(Functor cb)
{{std::unique_lock<std::mutex> lock(mutex_);pendingFunctors_.emplace_back(cb);}if(!isInLoopThread() || callingPendingFunctors_) {wakeup();}
}

可以看出来runInLoop主要是判断是否处于当前IO线程,是则执行这个函数,如果不是则将函数加入队列queueInLoop。在queueInLoop就会把cb放入pendingFunctors_

值得注意: wakeup();这个函数:

在构造函数中已经给它注册了回调函数:

wakeupChannel_->setReadCallback(std::bind(&EventLoop::handleRead,this));
wakeupChannel_->enableReading();

每一个eventloop都将监听wakeupchannel的EPOLLIN读事件了,mianreactor通过给subreactor写东西,通知其苏醒,那么handleRead里面是什么呢?

  • handleRead也是其中比较重要的一个回调了
//发送给subreactor一个读信号,唤醒subreactor
void EventLoop::handleRead()
{uint64_t one = 1;ssize_t n = read(wakeupFd_, &one, sizeof one);if(n != sizeof one){LOG_ERROR("EventLoop::handleRead() reads %d bytes instead of 8",n);}
}
  • 接着看看wakeup()源码
void EventLoop::wakeup()
{uint64_t one = 1;ssize_t n = write(wakeupFd_,&one,sizeof one);if(n != sizeof one){LOG_ERROR("EventLoop::wakeup() writes %lu bytes instead of 8 \n",n);}
}
  • 在析构的时候,关闭它
EventLoop::~EventLoop()
{wakeupChannel_->disableAll();wakeupChannel_->remove();::close(wakeupFd_);t_loopInThisThread = nullptr;
}

这就和上面提到的wakeupFd_联系起来了,
首先wakeupFd_实际上是调用eventfd,把这个wakeupFd_添加到poll中,在需要唤醒时写入8字节数据,
在构造函数中,也注册了它对应的回调函数wakeupChannel_->setReadCallback(std::bind(&EventLoop::handleRead,this));
此时poll返回,执行回调函数,然后执行在pendingFunctors_中的函数。

什么时候需要唤醒呢?

if(!isInLoopThread() || callingPendingFunctors_) 

前者还是比较好理解的,One Loop Per Thread 既然不在这个loop中,那就唤醒它;后者呢?从doPendingFunctors函数中我们可以看到callingPendingFunctors_= true;时,是表明正在执行回调函数,在loop()中可以看出执行完回调,又会阻塞在poller_->poll(kPollTimeMs,&activeChannels_);,如果再次调用queueInLoop,就需要再次唤醒才能继续执行新的回调doPendingFunctors

  • 判断是否在当前线程

首先通过以下代码获取了当前的loop的线程id,

threadId_(CurrentThread::tid())

实际上是在CurrentThread类中,通过调用SYS_gettid来获得,有关于SYS_gettid在我的另一篇博客,已经给了详细的介绍Linux—C/C++编程:syscall(系统调用)、SYS_gettid在muduo库中的使用以及static_cast

然后通过isInLoopThread()

bool isInLoopThread() const { return threadId_ == CurrentThread::tid(); }

进行比较,来判断是否在当前的线程

  • 接下来还有三个回调函数
//EventLoop的方法=> poller的方法
void EventLoop::updateChannel(Channel* channel)
{poller_->updateChannel(channel);
}void EventLoop::removeChannel(Channel* channel)
{poller_->removeChannel(channel);
}bool EventLoop::hasChannel(Channel* channel)
{return poller_->hasChannel(channel);
}

这就是调用了poller_的方法。

  • 最后的最后,就是退出循环了~
void EventLoop::quit()
{quit_ = true;if(!isInLoopThread()){wakeup();}
}

当然了,不在当前线程也是需要唤醒的。

EventLoop中有很多值得学习的点,但是最巧妙的就是wakeupFd_的设计:

传统的进程/线程间唤醒办法是用pipe或者socketpair,IO线程始终监视管道上的可读事件,在需要唤醒的时候,其他线程向管道中写一个字节,这样IO线程就从IO multiplexing阻塞调用中返回。pipe和socketpair都需要一对文件描述符,且pipe只能单向通信,socketpair可以双向通信。一方面它比 pipe 少用一个 fd,节省了资源;另一方面,wakeupFd_的缓冲区管理也简单得多,全部buffer只有定长8 bytes,不像 pipe 那样可能有不定长的真正 buffer。muduo库也没有采用生产者消费者的模型,采用了wakeupFd_这种巧妙的思想,在今后的学习中,我们也可以进一步的使用它。

最后附上代码地址:https://github.com/Cheeron955/mymuduo/tree/master

好了,关于muduo库三大核心组件之EventLoop类就到此结束了,三大核心组件我们都一一梳理介绍了,希望能够帮助到大家,接下来会对其余类进行一个梳理~

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

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

相关文章

SpringBoot内置插件的使用(jackson和lombok)

文章目录 引言I lombok(自动为属性生成构造器)II jacksonsee also引言 idea2021.2.2 已经捆绑安装jackson和lombok插件 I lombok(自动为属性生成构造器) Lombok能通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法。 https://p…

Spring Cloud Gateway详解

文章目录 Gateway搭建路由&#xff08;route&#xff09;断言&#xff08;Predicate &#xff09;自定义断言 过滤器&#xff08;filter&#xff09;自定义全局过滤器 引言 在传统的单体项目中&#xff0c;前端和后端的交互相对简单&#xff0c;只需通过一个调用地址即可实现。…

【C语言题解】用函数来模拟实现strlen()、strcpy()、strcmp()、strcat()

&#x1f970;欢迎关注 轻松拿捏C语言系列&#xff0c;来和 小哇 一起进步&#xff01;✊ 学习了函数后&#xff0c;老师让我们用函数来实现上面这四个字符串函数。 我们首先来了解一下这四个字符串函数&#xff1a; 1.strlen函数 用于获取字符串长度&#xff08;不包括末尾…

每日OJ题_贪心算法四②_力扣435. 无重叠区间

目录 力扣435. 无重叠区间 解析代码 力扣435. 无重叠区间 435. 无重叠区间 难度 中等 给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, endi] 。返回 需要移除区间的最小数量&#xff0c;使剩余区间互不重叠 。 示例 1: 输入: intervals [[1,2]…

IDEA安装使用Git

IDEA安装使用Git 1 Git下载与安装 2 在IDEA中使用Git 2.1 IDEA中配置Git 在IDEA中使用Git&#xff0c;本质上还是使用本地安装的Git软件&#xff0c;所以需要在IDEA中配置Git。 2.2 在IDEA中使用Git 2.2.1 获取Git仓库 在IDEA中使用Git获取仓库有两种方式: 本地初始化仓库从…

CentOS7 安装 Kamailio

https://www.kamailio.org/wiki/packages/rpms 官方文档说 yum -y install yum-utils yum-config-manager --add-repo https://rpm.kamailio.org/centos/kamailio.repo 但目前这样其实行不通 需要这样做&#xff1a; yum install --disablerepokamailio --enablerepokamai…

数据库出现死锁的解决方法参考

死锁引起的原因一般是多个用户并发访问数据库导致的问题&#xff0c;或是因为某个进程挂死以后资源未释放导致的。通过onstat –p可查看deadlks项大于0即表示历史总计死锁次数。对于被锁的表进行操作的时候会出现-143 ISAM error: deadlock detected的错误。当其他会话访问此表…

从JSON数据到Pandas DataFrame:如何解析出所需字段

目录 一、引言 二、JSON数据的基本结构 三、使用Pandas从JSON数据中读取数据 四、从DataFrame中解析出所需字段 解析对象字段 解析嵌套对象字段 解析数组字段 五、案例与代码示例 六、总结 一、引言 在数据分析和处理的日常工作中&#xff0c;我们经常需要从各种…

UBoat:一款功能强大的HTTP Botnet学习与研究工具

关于UBoat UBoat是一款功能强大的HTTP Botnet概念验证工具&#xff0c;该工具支持复刻一个现实场景中完整功能的Botnet测试环境&#xff0c;广大研究人员可以利用UBoat深入学习和研究Botnet的工作机制&#xff0c;以此来提升安全检测和保护策略。 功能介绍 1、基于C开发&…

luceda ipkiss教程 70:合并GDS版图

通过代码拼版&#xff1a; 所有代码如下&#xff1a; from si_fab import all as pdk from ipkiss3 import all as i3class Design1(i3.GDSCell):def _default_filename(self):return "Ring_Test.gds"def _default_name(self):return "Design1"class Des…

VTK官方例子

VTK官方例子 vtkMutableDirectedGraph #!/usr/bin/env python# noinspection PyUnresolvedReferences import vtkmodules.vtkInteractionStyle # noinspection PyUnresolvedReferences import vtkmodules.vtkRenderingOpenGL2 from vtkmodules.vtkCommonColor import vtkName…

Apache Flume事务

Apache Flume 中的事务处理是指 Flume Agent 在处理事件流时的一种机制&#xff0c;用于确保数据的可靠传输和处理。 1. 事务概述&#xff1a; Flume 中的事务是指一组事件的传输和处理&#xff0c;这些事件在传输过程中要么全部成功完成&#xff0c;要么全部失败&#xff0…

Jsp+Servlet实现图片上传和点击放大预览功能(提供Gitee源码)

前言&#xff1a;在最近老项目的开发中&#xff0c;需要做一个图片上传和点击放大的功能&#xff0c;在Vue和SpringBoot框架都有现成封装好的组件和工具类&#xff0c;对于一些上世纪的项目就没这么方便了&#xff0c;所以需要自己用原生的代码去编写&#xff0c;这里分享一下我…

01.Net基础知识

.Net的用途 Web、移动、云、桌面、游戏开发、物联网 &#xff08;IDE&#xff1a;集成开发环境&#xff09; .Net学习资源 Microsoft Learn、GitHub、G码云&#xff08;Gitee&#xff09; Visual Studio初步使用 1&#xff09;可创建的项目种类&#xff08;主要学习以下四…

Maven 的仓库、周期和插件

优质博文&#xff1a;IT-BLOG-CN 一、Maven 仓库 在Maven的世界中&#xff0c;任何一个依赖、插件或者项目构建的输出&#xff0c;都可以称为构建。Maven在某个统一的位置存储所有项目的共享的构建&#xff0c;这个统一的位置&#xff0c;我们就称之为仓库。任何的构建都有唯一…

经典权限五张表功能实现

文章目录 用户模块(未使用框架)查询功能实现步骤代码 新增功能实现步骤代码 修改功能实现步骤代码实现 删除功能实现步骤代码实现 用户模块会了&#xff0c;其他两个模块与其类似 用户模块(未使用框架) 查询功能 这里将模糊查询和分页查询写在一起 实现步骤 前端&#xff1…

翻译/润色找哪里比较专业,机构怎么选?

英文专业术语多&#xff0c;润色是很有必要的&#xff0c;大家可以选择专业的文章翻译润色服务&#xff0c;一定要挑选好正规的机构&#xff0c;这样的机构在出版过程中会为作者提供多项支持&#xff0c;对顺利发表是有帮助的。 科研领域英文论文专业润色包含这些内容&#xff…

基于Huffman编码的字符串统计及WPL计算

一、问题描述 问题概括&#xff1a; 给定一个字符串或文件&#xff0c;基于Huffman编码方法&#xff0c;实现以下功能&#xff1a; 1.统计每个字符的频率。 2.输出每个字符的Huffman编码。 3.计算并输出WPL&#xff08;加权路径长度&#xff09;。 这个问题要求对Huffman编码算…

德国Dürr杜尔机器人维修技巧分析

在工业生产中&#xff0c;杜尔工业机器人因其高效、精准和稳定性而备受青睐。然而&#xff0c;即便是最精良的设备&#xff0c;也难免会出现Drr机械手故障。 一、传感器故障 1. 视觉传感器故障&#xff1a;可能导致机器人无法正确识别目标物&#xff0c;影响工作效率。解决方法…

【页面】3D六边形

<!DOCTYPE html> <html> <head><title>3D正六边形</title><style>body {display: flex;justify-content: center;align-items: center;height: 100vh;margin: 0;}.container {perspective: 1000px;}.hexagon {width: 200px;height: 200px;…