1、概述
EventLoop相当于Reactor模型的Reactor反应堆的角色
poller和epollpoller相当于是多路分发器的角色,掌控epoll的所有操作
如果我们设置了setthreadnumber的多线程的反应堆类型,mainreactor就是处理新用户的连接,相当于accept,拿到新用户通信的fd,把fd和fd感兴趣的事件打包成channel,然后唤醒某个工作线程workreactor(subreactor)(采用轮询的方式),每个工作线程代表一个Eventloop。每个工作线程subreactor的EventLoop都监听一组channel,每一组channel都得在自己的eventloop线程去执行。
mainreactor如何给subreactor发配新连接,所采用的负载算法是一个轮询操作
mainreactor和subreactor如果没有事件发生的话,他们loop所在的线程都是阻塞的。
假如现在mainreactor监听到一个新用户的连接,得到了表示新用户连接的fd,以及感兴趣的事件的channel,它把这个channel怎么扔给subreactor呢?如何叫醒subReactor?
统一事件源原理
在libevent库中,采用的是socketpair
创建了一个本地socket的数组,与管道不同的是,这2个socket都是可读可写的,不像管道只能一端读,一端写。双向通信的socket,采用的是网络通信的模式。效率不如下面的eventfd。
在muduo库中,采用的是linux内核提供的比较新的eventfd
看到wait/notify,就应该反应过来,这就是线程间的通信机制。
这个线程可以notify通知其他线程起来做事情。eventfd是系统API,在内核可以直接notify用户空间的应用程序,让它的线程起来做事情,效率高。
2、EventLoop.h
#pragma once
#include <functional>
#include <vector>
#include <atomic>
#include <memory>
#include <mutex>
#include "noncopyable.h"
#include "Timestamp.h"
#include "CurrentThread.h"
class Channel;
class Poller;
//时间循环类 主要包含了两个大模块 Channel Poller(epoll的抽象)
class EventLoop:noncopyable
{
public:
using Functor=std::function<void()>;//定义一个回调的类型
//using代替typedef,进行类型的重命名
EventLoop();
~EventLoop();
//开启事件循环
void loop();
//退出事件循环
void quit();
Timestamp pollReturnTime()const {return pollReturnTime_;}
//在当前loop中执行cb
void runInLoop(Functor cb);
//把cb放入队列中,唤醒loop所在的线程,执行cb
void queueInLoop(Functor cb);
//用来唤醒loop所在的线程的
void wakeup();
//EventLoop的方法=>Poller的方法
void updateChannel(Channel* channel);
void removeChannel(Channel* channel);
bool hasChannel(Channel* channel);
//判断EventLoop对象是否在自己的线程里面
bool isInLoopThread()const{return threadId_==CurrentThread::tid();}//判断对象是否在创建它的EventLoop的线程里,如果不在的话就要queueloop唤醒自己的线程
private:
using ChannelList=std::vector<Channel*>;
void handleRead();//wake up
void doPendingFunctors();//执行回调
std::atomic_bool looping_;//原子操作,通过CAS实现的
std::atomic_bool quit_;//标识退出loop循环
const pid_t threadId_;//记录当前loop所在线程的id
Timestamp pollReturnTime_;//poller返回发生事件的channels的时间点
std::unique_ptr<Poller> poller_;//eventloop所管理的poller
int wakeupFd_; //linux内核的eventfd创建出来的
//主要作用,当前mainloop获取一个新用户的channel,通过轮询算法选择一个subloop,通过该成员变量唤醒subloop处理channel
std::unique_ptr<Channel> wakeupChannel_;//包括wakeupFd和感兴趣的事件
ChannelList activeChannels_;//eventloop所管理的channel
Channel* currentActiveChannel_;
std::atomic_bool callingPendingFunctors_;//标识当前loop是否有需要执行的回调操作
std::vector<Functor> pendingFunctors_;//存储loop需要执行的所有的回调操作
std::mutex mutex_;//互斥锁,用来保护上面vector容器的线程安全操作
};
3、 EventLoop.cc
因为在多个loop当中,都有可能同时调用另外一个loop相应的的runInloop,让它去执行回调,所以Vector<Functor>pendingFunctors_这个容器里面涉及并发的访问,需要通过锁的控制。
写执行回调的时候我们看到,它首先定义了一个局部的桩(vector),然后把1个变量置为true。然后加了1个锁,相当于把pendingFunctors_置空,
把放在里面的所有待执行的回调全部放到局部的vector对象里面,好处是:因为这个函数要一直不断从pendingFunctors_拿1个回调执行,拿1个回调执行,然后删掉,在这个时候,如果有很多回调,这里就一直锁着,导致mainloop无法及时向subloop注册新的Function,导致mainloop阻塞住向你下发channel的操作上。造成服务器的时延变长。
此操作相当于把待执行的vector装进这个局部变量,把pendingFunctors_给解放了,我可以在局部的vector取出回调慢慢的调用,也不妨碍mainloop向子loop写回调的时候,往pendingFunctors_装cb(call back),双方可以并发操作。
#include "EventLoop.h"
#include "Logger.h"
#include "Poller.h"
#include "Channel.h"
#include <sys/eventfd.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <memory>
// 防止一个线程创建多个EventLoop thread_local
// 当一个eventloop创建起来它就指向那个对象,在一个线程里再去创建一个对象,由于这个指针为空,就不创建
__thread EventLoop *t_loopInThisThread = nullptr;
// 定义默认的Poller IO复用接口的超时时间
const int kPollTimeMs = 10000; // 10s
// 创建wakeupfd,用来notify唤醒subReactor处理新来的channel
int createEventfd()
{
int evtfd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
if (evtfd < 0)
{
LOG_FATAL("eventfd error:%d\n", 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_)), currentActiveChannel_(nullptr)
{
LOG_DEBUG("EventLoop created %p in thread %d\n", this, thread_);
if (t_loopInThisThread) // 这个线程已经有loop了,就不创建了
{
LOG_FATAL("Another EventLoop %p exists in this thread %d\n", t_loopInThisThread, threadId_);
}
else // 这个线程还没有loop,创建
{
t_loopInThisThread = this;
}
// 设置wakeupfd的事件类型以及发生事件后的回调操作
wakeupChannel_->setReadCallback(std::bind(&EventLoop::handleRead, this));
// 每一个EventLoop都将监听wakeupchannel的EPOLLIN事件了
wakeupChannel_->enableReading();
}
EventLoop::~EventLoop()
{
wakeupChannel_->disableAll();
wakeupChannel_->remove();
::close(wakeupFd_);
t_loopInThisThread = nullptr;
}
// 开启事件循环 驱动底层的poller执行poll
void EventLoop::loop()
{
looping_ = true;
quit_ = false;
LOG_INFO("EventLoop %p start looping\n", this);
while (!quit_)
{
activeChannels_.clear();
// 监听两类fd 一种是client的fd,一种wakeupfd
pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);
for (Channel *channel : activeChannels_)
{
// Poller监听哪些channel发生事件了,然后上报给EventLoop,通知channel处理相应的事件
channel->handleEvent(pollReturnTime_); // 事先已经绑定好回调函数了
}
// 执行当前EventLoop事件循环需要处理的回调操作
/**
* IO线程mainLoop accept fd《=channrl subloop
* mainLoop 事先注册一个回调cb(需要subloop来执行) wakeup subloop后,执行下面的方法,执行之前mainloop注册的cb操作
*/
doPendingFunctors(); // mainloop注册回调给subloop。
}
LOG_INFO("EventLoop %p stop looping. \n", this);
looping_ = false;
}
// 退出事件循环 1.loop在自己的线程中调用quit
/**
* mainLoop
*
* 通过wakeupfd no ==================== 生产者-消费者的线程安全的队列
mainloop生产 subloop消费 逻辑好处理 但是muduo库没有这个 是通过eventfd这个系统调用创建的wakefd通信
来进行线程间直接notify唤醒
*
* subLoop1 subLoop2 subLoop3
*/
void EventLoop::quit()
{
quit_ = true;
// 如果是在其它线程中,调用的quit 在一个subloop(worker线程)中,调用了mainLoop(IO线程)的quit
// 调用mainloop的quit,这时候,应该给它唤醒 ,它就从poll返回回来,
// 当前线程在work线程 轮询的算法 派发channel
if (!isInLoopThread())
{
wakeup(); // 因为不知道主线程是什么情况,需要唤醒一下
}
}
// 在当前loop中执行cb
void EventLoop::runInLoop(Functor cb)
{
if (isInLoopThread()) // 在当前的loop线程中,执行cb
{
cb();
}
else // 在非当前loop线程中执行cb,就需要唤醒loop所在线程,执行cb
{
queueInLoop(cb);
}
}
// 把cb放入队列中,唤醒loop所在的线程,执行cb
void EventLoop::queueInLoop(Functor cb)
{
{
std::unique_lock<std::mutex> lock(mutex_);
pendingFunctors_.emplace_back(cb);
}
// 唤醒相应的,需要执行上面回调操作的loop的线程了
//||callingPendingFunctors_的意思是:当前loop正在执行回调,但是loop又有了新的回调
if (!isInLoopThread() || callingPendingFunctors_) // callingPendingFunctors_=true表示,当前的这个loop正在执行回调,没有阻塞在loop上
{
wakeup(); // 唤醒loop所在线程
}
}
void EventLoop::handleRead() // 就是读,写啥读啥无所谓,就是为了唤醒loop线程执行回调
{
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);
}
}
// 用来唤醒loop所在的线程的 向wakeupfd_写一个数据 wakeChannel就发生读事件,当前loop线程就会被唤醒
void EventLoop::wakeup()
{
unit64_t one=1;
ssize_t n=write(wakeupFd_,&one,sizeof one);
if(n!=sizeof one)
{
LOG_ERROR("EventLoop::wakeup() write %lu bytes instead of 8\n",n);
}
}
// 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);
}
void EventLoop::doPendingFunctors() // 执行回调
{
std::vector<Functor> functors;
callingPendingFunctors_=true;
{
std::unique_lock<std::mutex> lock(mutex_);
functors.swap(pendingFunctors_);//资源交换,把pendingFunctors_ 置为空
//不需要pendingFunctors_了 不妨碍 mainloop向 pendingFunctors_写回调操作cb
}
for(const Functor& functor:functors)
{
functor();//执行当前loop需要执行的回调操作
}
callingPendingFunctors_=false;
}