muduo源码分析之EventLoop::runInLoop
相关文件
muduo/net/EventLoop.h
muduo/net/EventLoop.cc
作用
任何一个线程,只要创建并运行了EventLoop,都称之为IO线程。runInLoop()使得IO线程能够执行某个用户任务回调。
如果用户在当前IO线程调用这个函数,回调会同步进行;如果用户在其他线程调用runInLoop(),回调函数会加入到队列中,IO线程会被唤醒来调用这个函数。
这样就能够轻易地在线程间调配任务,比如将回调函数都移到IO线程中执行,就可以在不使用锁的情况下保证线程安全。
使用
示例中,EventLoopThread类封装了IO线程,EventLoopThread创建了一个线程,在线程函数中创建了一个EvenLoop对象并调用EventLoop::loop。
在主线程调用runInLoop,即将runInThread添加到loop对象所在IO线程,让该IO线程执行。
#include <muduo/net/EventLoop.h>
#include <muduo/net/EventLoopThread.h>
#include <stdio.h>
using namespace muduo;
using namespace muduo::net;
void runInThread()
{
printf("runInThread(): pid = %d, tid = %d\n",
getpid(), CurrentThread::tid());
}
int main()
{
printf("main(): pid = %d, tid = %d\n",
getpid(), CurrentThread::tid());
EventLoopThread loopThread;
EventLoop* loop = loopThread.startLoop();
// 异步调用runInThread,即将runInThread添加到loop对象所在IO线程,让该IO线程执行
loop->runInLoop(runInThread);
sleep(1);
// runAfter内部也调用了runInLoop,所以这里也是异步调用
loop->runAfter(2, runInThread);
sleep(3);
loop->quit();
printf("exit main().\n");
}
runInLoop源码分析
上面说到,如果是其他线程调用runInLoop(),则把回调函数添加到队列中,那么就需要通知IO线程去执行。
而EventLoop一般阻塞在poll等待事件,就需要唤醒它。
EventLoop中专门包含了一个wakeupChannel_通道来做这件事。
wakeup通道初始化
在EventLoop的构造函数中初始化,使用eventfd创建文件描述符。
//创建文件描述符
int createEventfd()
{
int evtfd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
if (evtfd < 0)
{
LOG_SYSERR << "Failed in eventfd";
abort();
}
return evtfd;
}
//构造函数
EventLoop::EventLoop()
: looping_(false),
quit_(false),
eventHandling_(false),
callingPendingFunctors_(false),
iteration_(0),
threadId_(CurrentThread::tid()),
poller_(Poller::newDefaultPoller(this)),
timerQueue_(new TimerQueue(this)),
wakeupFd_(createEventfd()), //创建文件描述符
wakeupChannel_(new Channel(this, wakeupFd_)), //通道初始化
currentActiveChannel_(NULL)
{
LOG_DEBUG << "EventLoop created " << this << " in thread " << threadId_;
//保证one loop per thread
if (t_loopInThisThread)
{
LOG_FATAL << "Another EventLoop " << t_loopInThisThread
<< " exists in this thread " << threadId_;
}
else
{
t_loopInThisThread = this;
}
//设置回调函数
wakeupChannel_->setReadCallback(
std::bind(&EventLoop::handleRead, this));
// we are always reading the wakeupfd
wakeupChannel_->enableReading(); //监听读事件,注册到poller中
}
//回调函数
//只是把wakeupFd_的数据读出,清除事件触发
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";
}
}
添加到队列
非IO线程调用runInLoop时,将任务添加到队列中,再唤醒IO线程。
注意唤醒的条件:!isInLoopThread() || callingPendingFunctors_
非IO线程或者IO线程正在执行队列中的任务。
因为IO线程正在执行队列中的任务时,这些任务内可能又调用了runInLoop(),则需要再唤醒IO线程,否则队列中新添加的任务得不到及时执行。
void EventLoop::runInLoop(Functor cb)
{
if (isInLoopThread())
{
cb();
}
else
{
queueInLoop(std::move(cb));
}
}
void EventLoop::queueInLoop(Functor cb)
{
{
MutexLockGuard lock(mutex_);
pendingFunctors_.push_back(std::move(cb));//添加到队列
}
//pendingFunctors_是暴露给其他线程的,需要用锁
//callingPendingFunctors_在执行队列中的任务时为true
if (!isInLoopThread() || callingPendingFunctors_)
{
wakeup();
}
}
唤醒IO线程
往wakeupFd_文件写东西就可以使EventLoop从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";
}
}
执行任务
在doPendingFunctors中执行。
IO线程在执行完活跃通道的回调函数后,执行runInLoop放进队列中的任务。
执行时,先将回调任务放到局部变量中,再执行。
是为了减小锁的粒度(不会阻塞其他线程再在队列添加任务),同时避免死锁(因为执行的任务可能会在执行runInLoop,这时队列已经锁住的话就死锁了)。
doPendingFunctors()也没有重复执行执行直到队列为空,是为了避免死循环而无法处理IO事件。
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;
}
void EventLoop::doPendingFunctors()
{
std::vector<Functor> functors;
callingPendingFunctors_ = true;
//这里先将回调列表swap到局部变量functors再执行
{
MutexLockGuard lock(mutex_);
functors.swap(pendingFunctors_);
}
for (const Functor& functor : functors)
{
functor();
}
callingPendingFunctors_ = false;
}