muduo源码剖析 - EventLoop

 说明

一个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);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YanWenCheng_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值