陈硕Muduo库源码学习:EventLoop

        本人原本从事C++的学习的,现在进行即时通讯的网络开发学习,涉及到了muduo库顾muduo库进行深入学习,muduo库是给予reactor模型的并发处理的网络库,其广泛的利用了回调函数的特性。

         EventLoop的事件循环主要两个大部分:第一个部分,Poller监听socketfd、timerfd封装的Channel事件(网络事件、定时器事件),并执行对应IO事件的回调函数;第二个部分,在IO线程空闲时执行一些计算任务,充分利用线程资源。由于Poller是阻塞等待IO事件的,当有计算任务时需要被唤醒,使用eventfd的Channel的来实现。

针对EventLoop 的源码分析

  //该loop吃鱼的状态
  bool looping_; /* atomic */
  // 是否进行停止
  std::atomic<bool> quit_;
  
  bool eventHandling_; /* atomic */
  bool callingPendingFunctors_; /* atomic */
  int64_t iteration_;
  // 运行此线程的线程ID号
  const pid_t threadId_;
  Timestamp pollReturnTime_;
  std::unique_ptr<Poller> poller_;
  std::unique_ptr<TimerQueue> timerQueue_;
  int wakeupFd_;
  // unlike in TimerQueue, which is an internal class,
  // we don't expose Channel to client.
  std::unique_ptr<Channel> wakeupChannel_;
  boost::any context_;

  // scratch variables
  // 维护一个维护活跃的Channel对象
  ChannelList activeChannels_;
  // 
  Channel* currentActiveChannel_;
  // 互斥锁
  mutable MutexLock mutex_;
  std::vector<Functor> pendingFunctors_ GUARDED_BY(mutex_);

该对象的构造函数如下所示:

EventLoop::EventLoop()
  : looping_(false),               //该对象是否处于进行IO事件进行处理          
    quit_(false),                  //是否进行退出
    eventHandling_(false),         //是否正在处理IO事件
    callingPendingFunctors_(false),//是否正在进行计算任务
    iteration_(0),                 //处理的IO事件的次数                 

    threadId_(CurrentThread::tid()),                    //当前线程的tid
    poller_(Poller::newDefaultPoller(this)),             //Poller对象的
    timerQueue_(new TimerQueue(this)),                   //定时器队列
    wakeupFd_(createEventfd()),                          //eventfd用于唤醒阻塞的poll
    wakeupChannel_(new Channel(this, wakeupFd_)),        //eventfd 封装的channel对象
    currentActiveChannel_(NULL)                          //临时变量挡墙的就绪IO事件的channel数组
{
  LOG_DEBUG << "EventLoop created " << this << " in thread " << threadId_;
  if (t_loopInThisThread)
  {
    LOG_FATAL << "Another EventLoop " << t_loopInThisThread
              << " exists in this thread " << threadId_;
  }
  else
  {
    t_loopInThisThread = this;
  }
  //设置唤醒挡墙的Channel的可读事件的回调,并注册到Poller中
  wakeupChannel_->setReadCallback(
      std::bind(&EventLoop::handleRead, this));
  // we are always reading the wakeupfd
  wakeupChannel_->enableReading();
}

析构函数中,需要从显示地调用注销在Pollereventfd事件,并且关闭文件描述符。由于成员poller_和timerQueue_使用std::unique_ptr管理,不用手动处理资源释放的问题,在各自的析构函数中进行了资源释放处理。

EventLoop::~EventLoop()
{
  LOG_DEBUG << "EventLoop " << this << " of thread " << threadId_
            << " destructs in thread " << CurrentThread::tid();
  wakeupChannel_->disableAll(); 
  wakeupChannel_->remove();
  ::close(wakeupFd_);
  t_loopInThisThread = NULL;
}

进行事件的循环

函数EventLoop::loop()实现了事件循环流程,是EventLoop的核心:调用Poller->poll函数,返回就绪的有IO事件的channels,依次执行channels中的用户回调函数,接着处理IO线程中需要进行的额外计算任务。当没有IO事件时poll函数最多阻塞10s,会执行一次计算任务的处理,但是10s时间太长,因此使用eventfd来唤醒poll及时处理计算任务

void EventLoop::loop()
{
  assert(!looping_);
  // 判断是否为IO线程
  assertInLoopThread();
  looping_ = true;
  quit_ = false;  // FIXME: what if someone calls quit() before loop() ?
  LOG_TRACE << "EventLoop " << this << " start looping";

  while (!quit_)
  {
    // 清空当前活跃空间
    activeChannels_.clear();
    // 获取最新活跃的Channel添加到活跃的channel表中
    pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);
    ++iteration_;
    if (Logger::logLevel() <= Logger::TRACE)
    {
      printActiveChannels();
    }
    // TODO sort channel by priority
    //IO事件处理的标志位
    eventHandling_ = true;
    // 对其中的活跃的IO事件进行 处理
    for (Channel* channel : activeChannels_)
    {
      currentActiveChannel_ = channel;
      //执行某个事件的用户回调函数
      currentActiveChannel_->handleEvent(pollReturnTime_);
    }
    // 
    currentActiveChannel_ = NULL;
    eventHandling_ = false;
    // 执行位于其中的一些计算任务(任务队列中依次执行)
    doPendingFunctors();
  }

  LOG_TRACE << "EventLoop " << this << " stop looping";
  looping_ = false;
}

计算任务的执行过程:

不是在临界区中一次调用Functor,而是把回调列表swap到局部变量functors中,这样减小了临界区的长度(不会阻塞其他线程调用queueInLoop(),降低锁竞争),也避免了死锁的发生(Functor中可能再调用queueInLoop()函数


void EventLoop::doPendingFunctors()
{
  //确地任务队列
  std::vector<Functor> functors;
  //标志执行计算任务
  callingPendingFunctors_ = true;

  {
  // 加锁,把回调任务队列swap到临时变量中;降低临界区长度
  MutexLockGuard lock(mutex_);
  functors.swap(pendingFunctors_);
  }
  //次执行队列中的用户回调任务
  for (const Functor& functor : functors)
  {
    functor();
  }
  //设置的计算任务结束
  callingPendingFunctors_ = false;
}

添加计算任务到的IO下称中执行

主要是提升IO线程在空闲时的资源利用率,根据调用所处的线程划分为不同


void EventLoop::runInLoop(Functor cb)
{
  if (isInLoopThread())
  {
    cb();//如果是IO线程调用,则直接执行
  }
  else   //其他线程调用就放在的任务队列中之后再loop种植箱
  {
    queueInLoop(std::move(cb));
  }
}

void EventLoop::queueInLoop(Functor cb)
{
  {
    // 加锁,放入到指向的任务队列中
  MutexLockGuard lock(mutex_);
  pendingFunctors_.push_back(std::move(cb));
  }
  // 根据执行是否进行唤醒
  if (!isInLoopThread() || callingPendingFunctors_)
  {
    wakeup();
  }
}

        如果在IO线程即当前EventLoop所在线程,可以选择runInLoop函数立即执行,也可以选择调用queueInLoop将任务放入队列中。在其他线程中,不论调用runInLoop还是queueInLoop都将先把用户的回调任务放入pendingFunctors_队列中,等待loop中处理。

        由于IO线程平时阻塞在时间循环EventLoop::loop()中的poll函数上,为能让IO线程立刻执行用户回调,需要执行唤醒操作,有两种情况。第一种,是在其他线程调用,必须唤醒,否则阻塞直到超时才执行。第二种,在当前IO线程,但处于任务队列处理的过程中,Functor可能再调用queueInLoop,这种情况就必须执行wakeup,否则新加入到pendingFunctors_的cb就不能及时执行。

唤醒IO线程

在构造函数中注册唤醒channel的eventfd的可读事件、事件触发的回调函数。当调用wakeup()时,对eventfd执行write操作,那么poller_->poll将查询到当前eventfd上的可读事件,从而唤醒IO线程。接受事件之后,执行handleRead()回调从eventfd调用read,避免被再次唤醒。
 

void EventLoop::wakeup()
{  // 通过往eventfd写标志通知,让阻塞poll立马返回并执行回调函数
  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";
  }
}

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

定时器任务

      定时器任务添加通过EventLoop暴露给用户,封装函数更简单易用。TimeQueue::addTimer()内部实际是调用了EventLoop::runInLoop(),作为计算任务放入队列中在loop()函数中执行,保证线程安全;每添加一个定时器,都会更新一次TimerQueue中的timerfd的超时触发时间

// 在某个时刻执行
TimerId EventLoop::runAt(Timestamp time, TimerCallback cb)
{
  return timerQueue_->addTimer(std::move(cb), time, 0.0);
}
// 在延迟delay之后执行
TimerId EventLoop::runAfter(double delay, TimerCallback cb)
{
  Timestamp time(addTime(Timestamp::now(), delay));
  return runAt(time, std::move(cb));
}
// 间隔interval重复执行
TimerId EventLoop::runEvery(double interval, TimerCallback cb)
{
  Timestamp time(addTime(Timestamp::now(), interval));
  return timerQueue_->addTimer(std::move(cb), time, interval);
}

// 取消定时器任务
void EventLoop::cancel(TimerId timerId)
{
  return timerQueue_->cancel(timerId);
}

Channel的注册、更新、移除        

        Channel封装了socketfd、timerfd、eventfd等时间的封装处理定时器TimerQueue、EventLoop中的唤醒通道、TCP c/s端都封装了channel,将用户的文件描述符事件、事件处理回调注册到了Poller,以供loop监听事件

        Channel对象构造后,需要至少设置一个关注事件及其回调函数设置关注事件内部会调用EventLoop::updateChannel(this),将其注册到Poller上;更新关注事件也是。当关闭不再使用Channel,需要显示地关闭注册事件,并从Poller中注销,注销内部实现实际调用EventLoop::removeChannel(this)。因此,这三个操作实际都是在IO线程中执行的,不需要加锁。 三个函数的使用可以参考TimerQueue的构造函数和析构函数。

void EventLoop::updateChannel(Channel* channel)
{
  assert(channel->ownerLoop() == this);
  assertInLoopThread();
  poller_->updateChannel(channel);  // 将channel的事件和回调注册到Poller中
}

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);   // 将channel的事件和回调从Poller中移除
}


原文链接:https://blog.csdn.net/wanggao_1990/article/details/119062060  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值