Muduo库源码剖析(四)——EventLoop类

33 篇文章 2 订阅
15 篇文章 6 订阅

EventLoop类

要点

EventLoop 是Reactor的核心组件了,该类本质上不做任何工作,只是负责使用和协调Channel 类对象和 Poller类对象的工作。其中管理最重要的两个成员就是

one loop per thread顾名思义每个线程只能有一个EventLoop对象,因此EventLoop的构造函数会检查当前线程是否已经创建了其他EventLoop对象,遇到错误就终止程序(LOG_FATAL)。EventLoop的构造函数会记住本对象所属的线程threadId_)。创建了EventLoop对象的线程是IO线程,其主要功能是运行事件循环EventLoop:: loop()。EventLoop对象的生命期通常和其所属的线程一样长,它不必是heap对象。

事件循环必须在IO线程执行,因此EventLoop::loop()会检查这一precondition,防止其他线程来执行当前 EventLoop 对象的 loop(),比如下面的情况:

muduo::EventLoop *g_loop;
void threadFunc()
{
	g_loop->loop();
}
int main()
{
	muduo::EventLoop loop;
    g_loop = &loop;
    muduo::Thread t(threadFunc);
    t.start();
    t.join();
}

它在主线程创建了EventLoop对象,却试图在另一个线程调用其EventLoop::loop(),程序会因断言失效而异常终止

eventfd()
#include <sys/eventfd.h>
int eventfd(unsigned int initval, int flags);

eventfd()函数创建一个"eventfd对象",可以被用户空间应用程序用作事件等待/通知机制,同时也可以被内核用于通知用户空间应用程序事件的发生。该对象包含一个无符号64位整数(uint64_t)计数器,由内核维护。该计数器初始化为参数initval指定的值。

该函数优点是

  1. 高效:与其他进程间通信机制相比,如管道、消息队列等,eventfd() 的开销更小,因为它不需要内核和用户空间之间的数据复制。这使得它在高性能应用中更具优势。

  2. 可移植性:eventfd() 是 POSIX 标准的一部分,因此在各种 UNIX 系统中都可用。

  3. 支持异步事件通知:eventfd() 允许应用程序使用 epoll 或 select 等机制监视文件描述符,以便在文件描述符可读时通知应用程序。这使得它适用于实现高性能事件驱动的应用程序。

  4. 支持线程间通信:eventfd() 可以用于在不同线程之间传递信号,使得它在多线程应用程序中非常有用。

每个EventLoop 使用 eventfd()创建一个 wakeupFd 用于其他eventloop 来唤醒相应的自己 Eventloop,所谓唤醒,也就是给这个 wakeupFd 发送一个小数据,使对应Eventloop上的 epoll_wait检测到 wakeupFd 读事件,便解除阻塞。

代码详解

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

    EventLoop();
    ~EventLoop();

    // 开启事件循环
    void loop();
    // 退出事件循环
    void quit();

    Timestamp pollReturnTime() const { return pollReturnTime_; }

    // 在当前loop中执行cb
    void runInLoop(Functor cb);
    // 把cb放入队列中,唤醒loop操作(epoll_wait)所在线程执行cb
    void queueInLoop(Functor cb);

    // 用来唤醒loop所在线程
    void wakeup();

    // EventLoop的方法,转调用Poller对应方法
    void updateChannel(Channel *channel);
    void removeChannel(Channel *channel);
    void hasChannel(Channel *channel);

    // 判断当前线程是否为loop操作所在的线程
    bool isInLoopThread() const { return threadId_ == CurrentThread::tid(); }

private:
    void handleRead(); // 唤醒
    void doPendingFunctors(); // 执行回调

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

    std::atomic_bool looping_; // 原子操作 CAS实现
    std::atomic_bool quit_; // 标识是否退出loop循环

    // 调用某个loop对象的线程未必是进行loop操作的线程
    const pid_t threadId_; // 记录当前EventLoop进行loop操作所在线程的tid,确保Channel回调在其对应的evnetloop中执行 

    Timestamp pollReturnTime_; // poller返回发生事件的channels的时间点
    std::unique_ptr<Poller> poller_;

    int wakeupFd_; // 主要作用:当mainloop获取一个新用户的channel,通过轮询算法选择一个subloop,通过该fd唤醒对应subloop来执行Channel回调
    std::unique_ptr<Channel> wakeupChannel_; // 别的线程唤醒本loop线程使用的Channel

    ChannelList activeChannels_;

    std::atomic_bool callingPendingFunctors_; // 标识当前loop是否正在执行回调操作
    std::vector<Functor> pendingFunctors_; // 存储loop需要执行的所有回调函数
    std::mutex mutex_; // 保护 pendingFunctors_ 线程安全操作

};

// EventLoop.cc
#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>

// 每个线程都有自己独立的一份拷贝 thread_local
__thread EventLoop *t_loopInThisThread = nullptr;

// 定义默认Poller IO复用接口的超时时间
const int kPollTimeMs = 10000;

// 创建wakefd,用来notify唤醒subReactor 处理新来的Channel
int createEventfd()
{
    // EFD_CLOEXEC处理fork()导致的fd泄漏
    // eventfd 用来创建用于事件 wait / signal 的fd
    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()) // 当前loop的线程是构造时的线程
    , 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)
    {
        LOG_FATAL("Another EventLoop %p exists in this thread %d \n", t_loopInThisThread, threadId_);
    }
    else 
    {
        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;
}

void EventLoop::loop()
{
    looping_ = true;
    quit_ = false;

    LOG_INFO("EventLoop %p start looping \n", this);

    while(!quit_)
    {
        activeChannels_.clear();
        // 监听两类fd,一种时clientfd,一种是wakeupfd(main reactor 和 sub reactor通信用)
        // 发生事件的Channel都被加入到 activeChannels_  中
        pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);
        for(Channel *channel : activeChannels_)
        {
            // Poller监听哪些Channel发生事件,上报给EventLoop,通知Channel处理相应事件
            channel->handleEvent(pollReturnTime_)
        }

        // 执行当前EventLoop 事件循环需要处理的回调操作
        // IO线程 mainLoop accept fd 然后将连接fd对应的channel传递到 subloop
        // mainLoop事先注册回调cb(需要subloop所在线程中执行)
        // 通过 wakeupChannel_唤醒 subloop后 执行mainLoop事先注册回调cb
        doPendingFunctors();
    }

    LOG_INFO("EventLoop %p stop looping. \n", this);
    looping_ = false;
}

// 退出事件循环: 1) loop在自己的线程中调用quit; 2) 在非loop的线程中调用loop(构造loop的线程)的quit
void EventLoop::quit()
{
    quit_ = true;
    // 若在其他线程中调用quit(),则要唤醒当前loop线程进行退出
    // 场景如:在一个subloop中(worker) 中,调用了mainLoop(IO)的quit
    if(!isInLoopThread())
    {
        wakeup();
    }
}

void EventLoop::runInLoop(Functor cb)
{
    if(isInLoopThread()) // 在当前的loop线程中,则执行cb
    {
        cb();
    }
    else // 在非当前loop线程中执行cb,需要唤醒loop所在线程,执行cb
    {
        queueInLoop(cb);
    }
}
// 把cb放入队列中,唤醒loop操作(epoll_wait)所在线程执行cb
void EventLoop::queueInLoop(Functor cb)
{
    {
        std::unique_lock<std::mutex> lock(mutex_);
        pendingFunctors_.emplace_back(cb); // emplace_back减少拷贝开销
    }

    // 唤醒相应的,需要执行上面回调操作的loop线程
    // || callingPendingFunctors_ 表示: 当前loop正在执行回调,但是loop有了新的回调
    if(!isInLoopThread() || callingPendingFunctors_)
    {
        wakeup(); // 唤醒使得下一次epoll_wait检测到Channel事件到来
    }
}

void EventLoop::handleRead()
{
    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_写8 bytes 数据
// 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);  
    }
}

// EventLoop的方法,转调用Poller对应方法
void EventLoop::updateChannel(Channel *channel)
{
    poller_->updateChannel(channel);
}

void EventLoop::removeChannel(Channel *channel)
{
    poller_->removeChannel(channel);
}

void EventLoop::hasChannel(Channel *channel)
{
    poller_->hasChannel(channel);
}

void EventLoop::doPendingFunctors()
{
    std::vector<Functor> functors;
    callingPendingFunctors_ = true;

    {
        std::unique_lock<std::mutex> lock(mutex_);
        // 使得EventLoop::queueInLoop中往 pendingFunctors_ 加入回调不用等待这一批回调执行完就可加入,提高并发性
        functors.swap(pendingFunctors_);
    }

    for(const Functor &functor : functors)
    {
        functor(); // 执行当前loop需要执行的回调
    }
    callingPendingFunctors_ = false;
}

杂项

  • main Reactor和 sub Reactor之间可以加工作队列,main和sub Reactor都只和中间工作队列通信,使其解耦,但效率未必有eventfd()做通知机制高。

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值