说明
一个EventLoop类,任何一个线程,只要创建并运行了EventLoop,都称之为IO线程(为了方便使用,定义了EventLoopThread类,封装了IO线程).
__thread EventLoop* t_loopInThisThread = 0;
线程局部变量,在构造函数中赋值为当前对象. 保证一个线程只能有一个eventloop对象。
成员变量
主要的成员变量:
threadId:标识当前loop所在线程
poller:IO复用poll和epoll
timerQueue: 每个EventLoop都有个TimerQueue,定时器列表。
activeChannels_:Poller返回的活动通道,在loop中需要处理事件回调。
pendingFunctors_:IO线程需要执行的一些计算任务,在每次poll被唤醒且处理完具体通道事件后,进行计算任务。
wakeupChannel_:eventfd的通道, 对他的写事件可以唤醒poll去处理计算任务,两种情况需要唤醒:(1)poll里面的通道没有事件发生(pendingFunctors_里面的任务自然就没法处理),(2)正在处理pendingFunctors_的计算任务,又添加了新的任务,新任务需要下一次poll被唤醒后才能执行(见doPendingFunctors_函数实现)
typedef std::vector<Channel*> ChannelList; bool looping_; /* atomic */ //bool类型,表示是否处于loop()函数执行中,它的值变化都在loop()函数中 std::atomic<bool> quit_; //bool类型,判断是否退出,loop()中循环的结束条件 bool eventHandling_; /* atomic */ //表示是否处于处理事件事件中,在loop()中poll()选择需要处理的通道(事件)后,在对这些事件的处理过程中把这个变量置为true bool callingPendingFunctors_; /* atomic */ //是否处于doPendingFunctors()函数中 int64_t iteration_; //在构造函数中赋初值为0,以后只在loop()中有用到,即每次poll()后+1,应该是记录poll()的次数吧 const pid_t threadId_; //当前对象所属线程id Timestamp pollReturnTime_; //调用poll(),返回时间戳 std::unique_ptr<Poller> poller_; //每个EventLoop都会有一个Poller, std::unique_ptr<TimerQueue> timerQueue_; //每个EventLoop都有个TimerQueue,定时器列表,作为定时器 //用于eventfd,在构造函数中用createEventfd()生成eventfd赋值,作为线程唤醒需要使用的fd //进程间通信有几种方式:pipe,socket,eventfd,线程间通信还有条件变量等 int wakeupFd_; // unlike in TimerQueue, which is an internal class, // we don't expose Channel to client. std::unique_ptr<Channel> wakeupChannel_; //该通道将会纳入poller_来管理,用来唤醒当前线程,这是一个内部channel,不会暴露给用户 boost::any context_; //boost::any,任意数据类型,C++17也加入C++标准 // scratch variables ChannelList activeChannels_; //Poller返回的活动通道 Channel* currentActiveChannel_; //当前正在处理的活动通道 mutable MutexLock mutex_; std::vector<Functor> pendingFunctors_ GUARDED_BY(mutex_); //Functor的队列,这个就是需要处理的functor队列,在doPendingFunctors()函数中处理完就清空了,要理解需要结合runInLoop(),queueInLoop()等函数
核心方法
1. loop() :
循环调用poller_->poll(),返回活动的通道channel,处理这些通道的回调函数 。最后doPendingFunctors(), 处理其他线程或者当前线程异步增加的需要处理的计算任务。
void EventLoop::loop()
{
assert(!looping_);
assertInLoopThread();
looping_ = true;
quit_ = false; // FIXME: what if someone calls quit() before loop() ?
LOG_TRACE << "EventLoop " << this << " start looping";
while (!quit_)
{
activeChannels_.clear();
pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);
++iteration_;
if (Logger::logLevel() <= Logger::TRACE)
{
printActiveChannels();
}
// TODO sort channel by priority
eventHandling_ = true;
for (Channel* channel : activeChannels_)
{
currentActiveChannel_ = channel;
currentActiveChannel_->handleEvent(pollReturnTime_);
}
currentActiveChannel_ = NULL;
eventHandling_ = false;
doPendingFunctors();
}
LOG_TRACE << "EventLoop " << this << " stop looping";
looping_ = false;
}
2. doPendingFunctors()
并非临界区pendingFunctors_(因为其他线程也可以runInLoop) 内依次调用Functor, 而是把回调列表swap到functors中,这样一方面减小了临界区的长度(意味其他线程不会阻塞在runInLoop()),另一方面也避免了死锁(因为Functor可能再次调用queueInLoop());
void EventLoop::doPendingFunctors()
{
std::vector<Functor> functors;
callingPendingFunctors_ = true;
{
MutexLockGuard lock(mutex_);
functors.swap(pendingFunctors_);
}
for (const Functor& functor : functors)
{
functor();
}
callingPendingFunctors_ = false;
}
3. runInLoop()
前面两个函数做了两个事情:
1. 处理活动channel回调事件
2. 处理IO线程计算任务(pendingFunctors_任务容器)
而runInLoop就是其他线程往pendingFunctors_任务容器添加计算任务的功能。
注意:如果在当前IO线程调用runInLoop.则同步调用cb()回调函数 ,如果在其他线程调用runInLoop.则调用queueInLoop(cb)异步地将cb添加到pendingFunctors_任务容器。
其他线程添加完任务后,需要判断是否唤醒此线程loop去处理计算任务。(两种情况见成员变量eventfd的解释)
void EventLoop::runInLoop(Functor cb)
{
if (isInLoopThread())
{
cb();
}
else
{
queueInLoop(std::move(cb));
}
}
//如果调用当前函数的线程不是当前IO线程,则需要唤醒,或者是当前线程,但此时正在调用pendingfunctor,也需要唤醒
void EventLoop::queueInLoop(Functor cb)
{
{
MutexLockGuard lock(mutex_);
pendingFunctors_.push_back(std::move(cb));
}
if (!isInLoopThread() || callingPendingFunctors_)
{
wakeup();
}
}
4. wakeup() 和 handleRead()
构造函数
wakeupChannel_->setReadCallback(
std::bind(&EventLoop::handleRead, this));
// we are always reading the wakeupfd
wakeupChannel_->enableReading();
eventfd的作用体现在此处,构造函数将eventfd注册给当前loop。eventfd就可以控制poll随时被唤醒去处
// 唤醒当前对象线程,实际是调用write(),这样loop()函数中的poll()就会选到当前线程,然后执行
void EventLoop::wakeup()
{
uint64_t one = 1;
ssize_t n = sockets::write(wakeupFd_, &one, sizeof one);
if (n != sizeof one)
{
LOG_ERROR << "EventLoop::wakeup() writes " << n << " bytes instead of 8";
}
}
//唤醒处理函数,调用read,里面的内容没有什么意义
void EventLoop::handleRead()
{
uint64_t one = 1;
ssize_t n = sockets::read(wakeupFd_, &one, sizeof one);
if (n != sizeof one)
{
LOG_ERROR << "EventLoop::handleRead() reads " << n << " bytes instead of 8";
}
}
其他方法
1. updateChannel() 和 removeChannel()。
channel是在poller中管理的,eventloop只是一个中转作用。
// 断言当前对象线程正在执行,调用poller_->updateChannel(channel);
void EventLoop::updateChannel(Channel* channel)
{
assert(channel->ownerLoop() == this);
assertInLoopThread();
poller_->updateChannel(channel);
}
// 保证能找到这个channel,调用poller_->removeChannel(channel);移除这个channel
void EventLoop::removeChannel(Channel* channel)
{
assert(channel->ownerLoop() == this);
assertInLoopThread();
if (eventHandling_)
{
assert(currentActiveChannel_ == channel ||
std::find(activeChannels_.begin(), activeChannels_.end(), channel) == activeChannels_.end());
}
poller_->removeChannel(channel);
}
2. 定时器列表相关
三个定时器函数,创建一个定时器任务挂在定时器列表中,细节见TimerQueue。
//就是把cb()加入定时器队列,在time时间执行
TimerId EventLoop::runAt(Timestamp time, TimerCallback cb)
{
return timerQueue_->addTimer(std::move(cb), time, 0.0);
}
//把cd()加入定时器队列,当前时间+delay后执行
TimerId EventLoop::runAfter(double delay, TimerCallback cb)
{
Timestamp time(addTime(Timestamp::now(), delay));
return runAt(time, std::move(cb));
}
//把cb()加入定时器队列,每隔interval间隔时间执行一次
TimerId EventLoop::runEvery(double interval, TimerCallback cb)
{
Timestamp time(addTime(Timestamp::now(), interval));
return timerQueue_->addTimer(std::move(cb), time, interval);
}
//调用poller_->cancel(timerId),结束这个定时器任务
void EventLoop::cancel(TimerId timerId)
{
return timerQueue_->cancel(timerId);
}