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

本文主要是介绍[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类就到此结束了,三大核心组件我们都一一梳理介绍了,希望能够帮助到大家,接下来会对其余类进行一个梳理~

这篇关于[muduo网络库]——muduo库三大核心组件之EventLoop类(剖析muduo网络库核心部分、设计思想)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

JS常用组件收集

收集了一些平时遇到的前端比较优秀的组件,方便以后开发的时候查找!!! 函数工具: Lodash 页面固定: stickUp、jQuery.Pin 轮播: unslider、swiper 开关: switch 复选框: icheck 气泡: grumble 隐藏元素: Headroom

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

hdu1496(用hash思想统计数目)

作为一个刚学hash的孩子,感觉这道题目很不错,灵活的运用的数组的下标。 解题步骤:如果用常规方法解,那么时间复杂度为O(n^4),肯定会超时,然后参考了网上的解题方法,将等式分成两个部分,a*x1^2+b*x2^2和c*x3^2+d*x4^2, 各自作为数组的下标,如果两部分相加为0,则满足等式; 代码如下: #include<iostream>#include<algorithm

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

如何在页面调用utility bar并传递参数至lwc组件

1.在app的utility item中添加lwc组件: 2.调用utility bar api的方式有两种: 方法一,通过lwc调用: import {LightningElement,api ,wire } from 'lwc';import { publish, MessageContext } from 'lightning/messageService';import Ca

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

ASIO网络调试助手之一:简介

多年前,写过几篇《Boost.Asio C++网络编程》的学习文章,一直没机会实践。最近项目中用到了Asio,于是抽空写了个网络调试助手。 开发环境: Win10 Qt5.12.6 + Asio(standalone) + spdlog 支持协议: UDP + TCP Client + TCP Server 独立的Asio(http://www.think-async.com)只包含了头文件,不依

怎么让1台电脑共享给7人同时流畅设计

在当今的创意设计与数字内容生产领域,图形工作站以其强大的计算能力、专业的图形处理能力和稳定的系统性能,成为了众多设计师、动画师、视频编辑师等创意工作者的必备工具。 设计团队面临资源有限,比如只有一台高性能电脑时,如何高效地让七人同时流畅地进行设计工作,便成为了一个亟待解决的问题。 一、硬件升级与配置 1.高性能处理器(CPU):选择多核、高线程的处理器,例如Intel的至强系列或AMD的Ry

poj 2976 分数规划二分贪心(部分对总体的贡献度) poj 3111

poj 2976: 题意: 在n场考试中,每场考试共有b题,答对的题目有a题。 允许去掉k场考试,求能达到的最高正确率是多少。 解析: 假设已知准确率为x,则每场考试对于准确率的贡献值为: a - b * x,将贡献值大的排序排在前面舍弃掉后k个。 然后二分x就行了。 代码: #include <iostream>#include <cstdio>#incl

poj 3181 网络流,建图。

题意: 农夫约翰为他的牛准备了F种食物和D种饮料。 每头牛都有各自喜欢的食物和饮料,而每种食物和饮料都只能分配给一头牛。 问最多能有多少头牛可以同时得到喜欢的食物和饮料。 解析: 由于要同时得到喜欢的食物和饮料,所以网络流建图的时候要把牛拆点了。 如下建图: s -> 食物 -> 牛1 -> 牛2 -> 饮料 -> t 所以分配一下点: s  =  0, 牛1= 1~