重写muduo之EventLoop模块

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值