手写C++muduo库(EventLoop事件循环)

339 阅读7分钟

EventLoop相当于reactor模型的reactor反应堆的角色
poller和epollpoller相当于是多路分发器的角色,掌控epoll的所有操作

image.png

image.png 因为我们在使用muduo库的时候要自己创建一个eventloop对象,在使用过程中如果不调用TcpServer的setthreadnumber,那么整个服务器结构中只有一个主线程,也就是自己创建的eventloop主事件循环,也就是mainreactor所在的线程。

最起码得有一个线程来支撑。

如果我们设置了setthreadnumber的多线程的反应堆类型,mainreactor就是监听新用户的连接,处理accept,拿到新用户通信的fd,把fd感兴趣的事件打包成channel,然后唤醒某个工作线程workreactor(subreactor)(轮询的方式),一个subreactor就代表一个eventloop。每个eventloop都监听一组channel,每一组channel都得在自己的eventloop线程里去执行。代码上通过threadId实现(记录了创建eventloop对象所在的线程id,和当前线程一比较,就可以知道eventloop在不在自己的线程里面)

mainreactor和subreactor
mainreactor给subreactor如何分配新连接所采用的负载算法就是轮询操作。如果没有事件发生的话,他们loop所在的线程都是阻塞的。
假如现在mainreactor监听到一个新用户的连接,得到了表示新用户连接的fd,以及感兴趣的事件的channel,它把这个channel怎么扔给subreactor呢?如何叫醒???

image.png 统一事件源的原理。
在libevent库中,采用的是socketpair,主loop和子loop之间的通讯机制,都创建了socketpair,走的是网络通信模式 image.png 在muduo库中,采用的是linux提供的eventfd image.png 这就是线程间的通信机制。用的eventfd系统调用,这个线程可以notify通知其他线程起来做事情。eventfd是系统API,在内核可以直接notify应用空间的应用程序,让它的线程起来做事情,效率高。

EventLoop.h

#pragma once

#include<functional>
#include<vector>
#include<atomic>
#include<memory>
#include<mutex>

#include "noncopyable.h"
#include "Timestamp.h"
#include "CurrentThread.h"
//事件循环类,主要包含了两大模块Channel和Poller(epoll的抽象)

class Channel;
class Poller;

class EventLoop
{
public:
    using Functor = std::function<void()>;//相当于定义了一个回调的类型

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

private:
    void handleRead();//处理wake up唤醒相关的逻辑
    void doPendingFunctors();//执行回调

    using ChannelList = std::vector<Channel*>;

    //下面两个变量是和这个事件循环本身是否继续loop下去相关的控制变量
    std::atomic_bool looping_;  //原子操作,通过CAS实现
    std::atomic_bool quit_; //标志退出loop循环
    
    const pid_t threadId_;  //记录当前loop所在的线程id
   
    //和poller有关
    Timestamp pollReturnTime_;  //poller返回发生事件channels的时间点
    std::unique_ptr<Poller> poller_; //eventloop所管理的poller 

    int wakeupFd_;  //主要作用,当mainloop获取一个新用户的channel,通过轮询算法选择一个subloop,通过该成员唤醒subloop(用的eventfd系统调用)处理channel
    std::unique_ptr<Channel> wakeupChannel_;    //包括wakeupFd和感兴趣的事件 

    //存储eventloop下所有的channel相关的
    ChannelList activeChannels_;
    Channel *currentActiveChannel_;

    //loop上需要执行的回调操作
    std::atomic_bool callingPendingFunctors_;   //标识当前Loop是否有需要执行的回调操作
    std::vector<Functor> pendingFunctors_; //存储loop需要执行的所有的回调操作
    std::mutex mutex_; //互斥锁,用来保护上面vector容器的线程安全的操作


};

image.png 写执行回调的时候我们看到,它首先定义了一个局部的桩(vector),然后把1个变量置为true。然后加了1个锁,相当于把pendingFunctors_置空,把放在里面的所有待执行的回调全部放到局部的vector对象里面,把pendingFunctors_就解放了,这样就可以从局部的vector里面取出回调慢慢去调用,也不会妨碍mainloop向子loop写回调的时候往pendingFunctors_里面装callback,这样双方就可以并发操作。不这样做的话:因为这个函数要一直不断从pendingFunctors_拿1个回调执行拿1个回调执行,然后删掉,在这个时候,如果有很多回调,这里就一直锁着,导致mainloop阻塞住向你下发channel的操作上。造成服务器的时延变长。

EventLoop.cc

#include<sys/eventfd.h>
#include<unistd.h>
#include<fcntl.h>
#include<errno.h>
#include<memory.h>

#include "EventLoop.h"
#include "Logger.h"
#include "Poller.h"
#include "Channel.h"

//防止一个线程创建多个eventloop   
//__thread:就是thread_local机制,如果不加就是全局变量,所有线程所共享,我们要一个线程就有一个eventloop
//当一个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_))
{
    LOG_DEBUG("EventLoop created %p in thread %d \n", this,threadId_);
    if(t_loopInThisThread)//这个线程已经有loop了,就不创建了 
    {
        LOG_FATAL("Another EventLoop %p exist in this thread %d \n",t_loopInThisThread,threadId_);
    }
    else//这个线程还没有loop,创建 
    {
        t_loopInThisThread = this;
    }

    //设置wakeup的事件类型以及发生事件后的回调操作
    wakeupChannel_->setReadCallBack(std::bind(&EventLoop::handleRead,this));
    //每一个eventloop都将监听wakeupchannel的Epollin读事件了
    wakeupChannel_->enableReading();
}
EventLoop::~EventLoop()
{
    wakeupChannel_->disableAll();
    wakeupChannel_->remove();
    ::close(wakeupFd_);
    t_loopInThisThread = nullptr;
}

//开启事件循环
void EventLoop::loop()
{
    looping_ = true;
    quit_ = false;
    LOG_INFO("EventLoop %p start looping \n", this);

    while(!quit_)
    {
        activeChannels_.clear();
        //监听两类fd,一种是client的fd,一种是wakeup的fd
        pollReturnTime_ = poller_->poll(kPollTimeMs,&activeChannels_);
        for(Channel* channel : activeChannels_)
        {
            //Poller可以监听哪些channel发生事件了,然后上报给EventLoop,EventLoop通知channel处理相应的事件
            channel->handleEvent(pollReturnTime_);
        }
        //执行当前EventLoop需要处理的回调操作
        /**
         * IO线程就是mainLoop(mainreactor),主要做的是accept接收新用户的连接,接收新用户的连接以后,accept会返回和客户端专门通信用的fd
         * 我们肯定会用channel来打包fd,因为mainLoop只做新用户的连接,而已连接用户的channel得分发给subloop,如果说从来没有调用过muduo库
         * 的setthreadnumber,也就是说muduo库只有一个loop就是mainloop,不仅仅需要监听新用户的连接,还要负责已连接用户的读写事件,我们现
         * 在都是多核的CPU肯定会调用setthreadnumber,那么mainloop拿到和新用户通信的channel以后,就会wakeup唤醒某一个subloop,mainloop
         * 事先注册一个回调,这个回调就是一个cb,这个cb需要subloop来执行,但是此时的subloop还在阻塞,mainloop在wakeup以后(通过eventfd把
         * subloop唤醒),要执行回调,回调都在pendingFunctors_里写的,回调就是谁唤醒你让你做事情的,做什么事情呢,mainloop要事先注册一个回调cb
         * 所以mainloop唤醒subloop以后,执行下面的方法,执行之前mainloop注册的cb
         */
        doPendingFunctors();
    }
    LOG_INFO("EventLoop %p stop looping. \n", this);
    looping_ = false;
}
//退出事件循环 1. loop在自己的线程中调用quit,2.在非loop的线程中,调用loop的quit
/**
 *              mainLoop
 * 
 *              通过wakeupfd                     no ==================== 生产者-消费者的线程安全的队列
                                                mainloop生产 subloop消费  逻辑好处理 但是muduo库没有这个 是通过wakefd通信 
                                                线程间直接notify唤醒 
 * 
 *  subLoop1     subLoop2     subLoop3    
 */ 
void EventLoop::quit()
{
    quit_ = true;
    if(!isInLoopThread()) //2. 如果实在其他线程中调用的quit,比如在一个subloop中调用了mainloop的quit
    {
        wakeup();//因为不知道主线程是什么情况,需要唤醒一下 
    }
}

//在当前loop中执行cb
void EventLoop::runInLoop(Functor cb)
{
    if(isInLoopThread()) //在当前的loop线程中执行callback(cb)
    {
        cb();
    }
    else //在非当前loop线程中执行cb,那就需要唤醒loop所在线程执行cb
    {
        queueInLoop(cb);
    }
}
//把cb放入队列中,唤醒loop所在的线程,执行cb
void EventLoop::queueInLoop(Functor cb)
{
    //因为多个loop可能同时去调用另一个loop让它执行回调,所以vector涉及了并发访问,需要通过锁控制
    {
        std::unique_lock<std::mutex> lock(mutex_);
        //push_back是拷贝构造,emplace_back是直接构造
        pendingFunctors_.emplace_back(cb);
    }
    //唤醒相应的,需要执行上面回调操作的loop线程了
    //|| callingPendingFunctors_的意思是:当前loop正在执行回调,但是loop又有了新的回调,
    if(!isInLoopThread() || callingPendingFunctors_)
    {
        //唤醒loop所在线程
        wakeup();
    }
}


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 %lu bytes instead of 8", n);
  }
}

//用来唤醒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 %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)
{
    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;
}