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线程,否则队列中新添加的任务得不到及时执行。
image

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事件。
image

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;
}

posted @ 2021-05-20 11:22  零十  阅读(369)  评论(0编辑  收藏  举报