之前一直不是很理解muduo里实现的异步回调,总算看懂了记录一下。
这是muduo最核心的事件循环:
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;
}
总的来说就是在while循环中做两件事情:
1、处理活跃的事件
2、处理其他任务
首先看看doPendingFunctors()
:
void EventLoop::doPendingFunctors()
{
std::vector<Functor> functors;
callingPendingFunctors_ = true;
{
MutexLockGuard lock(mutex_);
functors.swap(pendingFunctors_);
}
for (const Functor& functor : functors)
{
functor();
}
callingPendingFunctors_ = false;
}
大概就是说弄了一个vector
去接收任务,然后依次处理,我们来看看这个pendingFuncotrs_
是哪来的:
void EventLoop::runInLoop(Functor cb)
{
if (isInLoopThread())
{
cb();
}
else
{
queueInLoop(std::move(cb));
}
}
如果有其他线程将任务放入了当前IO线程的runInLoop()
中,那么就会执行queueInLoop()
:
void EventLoop::queueInLoop(Functor cb)
{
{
MutexLockGuard lock(mutex_);
pendingFunctors_.push_back(std::move(cb));
}
if (!isInLoopThread() || callingPendingFunctors_)
{
wakeup();
}
}
这个时候会把任务塞入pendingFunctors_
中,如果!isInLoopThread() || callingPendingFunctors_
,那么则调用wakeup()
唤醒:
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";
}
}
也就是说满足条件!isInLoopThread()
或者callingPendingFunctors_
的时候,会往wakeupFd_
里面写数据:
第一个条件:可以理解为当前IO线程阻塞于返回活跃事件的poll()
当中,需要往wakeupFd_
里写东西,poll()
会立即返回,从而返回活跃事件退出阻塞。
第二个条件:可以理解为当前IO线程正在执行这些额外任务。为了避免任务执行完后又阻塞在了poll()
处,就让wakeupFd_
可读,以免任务被延迟执行。
所谓异步唤醒就是解除阻塞状态。