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指定的值。
该函数优点是
-
高效:与其他进程间通信机制相比,如管道、消息队列等,eventfd() 的开销更小,因为它不需要内核和用户空间之间的数据复制。这使得它在高性能应用中更具优势。
-
可移植性:eventfd() 是 POSIX 标准的一部分,因此在各种 UNIX 系统中都可用。
-
支持异步事件通知:eventfd() 允许应用程序使用 epoll 或 select 等机制监视文件描述符,以便在文件描述符可读时通知应用程序。这使得它适用于实现高性能事件驱动的应用程序。
-
支持线程间通信: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()
做通知机制高。