muduo_net代码剖析之EventLoop

TCP网络编程本质

TCP网络编程最本质的是处理三个半事件

  1. 连接建立:服务器accept(被动)接收连接,客户端connect(主动)发起连接
  2. 连接断开:主动断开(close、shutdown),被动断开(read返回0)
  3. 消息到达:文件描述符可读
  4. 消息发送完毕:这算半个。对于低流量的服务,可不必关心这个事件;这里的发送完毕是指数据写入操作系统缓冲区,将由TCP协议栈负责数据的发送与重传,不代表对方已经接收到数据

在这里插入图片描述

1、EventLoop之one loop per thread

one loop per thread意味着每个线程只能有一个EventLoop对象,用变量
__thread EventLoop* t_loopInThisThread = 0;
表示,在创建EventLoop对象时将t_loopInThisThread赋值,以后再创建时就可以检查这个变量,如果已经赋值就说明当前线程已经创建过EventLoop对象了。线程调用静态函数EventLoop::getEventLoopOfCurrentThread就可以获得当前线程的EventLoop对象的指针了。

isInLoopThread():判断当前线程是否是创建EventLoop对象的线程

bool isInLoopThread() const 
{ 
  return threadId_ == CurrentThread::tid(); 
}

构造函数:

  1. 初始化wakeupChannel_,它与queueInLoop()中的wakeup()有关,后面再详细介绍
  2. 初始化poller_
  3. 初始化timerQueue_
EventLoop::EventLoop()
  : looping_(false), //没调用loop()函数
    quit_(false),
    eventHandling_(false),
    callingPendingFunctors_(false),
    iteration_(0),
    threadId_(CurrentThread::tid()), //当前线程的真实ID
    poller_(Poller::newDefaultPoller(this)), //创建默认的Poller,即epoll
    timerQueue_(new TimerQueue(this)), //时间队列
    wakeupFd_(createEventfd()), //创建用于[等待/通知]的Eventfd
    wakeupChannel_(new Channel(this, wakeupFd_)), //用Eventfd创建通道
    currentActiveChannel_(NULL)
{
  LOG_DEBUG << "EventLoop created " << this << " in thread " << threadId_;

  //如果当前线程已经创建了EventLoop对象,终止(LOG_FATAL)
  if (t_loopInThisThread)
  {
    LOG_FATAL << "Another EventLoop " << t_loopInThisThread
              << " exists in this thread " << threadId_;
  }
  else
  {
    t_loopInThisThread = this;
  }
  
  //给wakeupChannel_设置回调函数handleRead
  wakeupChannel_->setReadCallback(
      std::bind(&EventLoop::handleRead, this));
  //激活读事件
  wakeupChannel_->enableReading();
}

2、EventLoop之事件循环loop()

与libevent的event_base类似,EventLoop也是一个事件循环,调用loop()函数执行事件循环,其中:loop()函数本质上是一个while循环,在while循环中调用了epoll_wait/poll监听注册在EventLoop上的Channel。

下面是loop()函数的时序图:
1:调用了EventLoop::loop()
2:内部调用了Poller::poll()
3与4:表示poll()函数返回后将触发的Channel填充到activeChannels_中
5:遍历activeChannels_中的每个激活的Channel,执行该Channel的回调函数
6:doPendingFunctors(),即处理pending队列中的回调函数
7:继续下一次Poller::poll()
在这里插入图片描述

loop()代码解读:

  1. loop()中调用poll(),当程序没有发生事件时,[阻塞]在此处
  2. pool()函数返回后,activeChannels_被赋值为被激活的通道
    其中,poll()函数返回有两种情况:
    ① 注册的read/write事件被激活
    ② wakeupChannel_被wakeup()
  3. poll()函数返回后,会执行两种操作
    ① 执行activeChannels_中激活的事件回调函数
    ② 调用doPendingFunctors():执行queueInLoop放入[等待队列]中的回调函数
//事件循环,该函数不能跨线程调用,只能在创建该对象的线程中调用
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();
	
	//调用poll(),当程序没有发生事件时,[阻塞]在此处
	//pool()函数返回后,activeChannels_被赋值为被激活的通道
	//其中,poll()函数返回有两种情况:
	// 1.注册的read/write事件被激活
	// 2.wakeupChannel_被wakeup()
	//poll()函数返回后,会执行两种操作
	// 1.执行activeChannels_中激活的事件回调函数
	// 2.调用doPendingFunctors():执行queueInLoop放入[等待队列]中的回调函数
    pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);
    ++iteration_;
    if (Logger::logLevel() <= Logger::TRACE)
    {
      printActiveChannels();
    }
    // TODO sort channel by priority
    eventHandling_ = true;
	//遍历每一个活动通道,并处理事件handleEvent
    for (Channel* channel : activeChannels_)
    {
      currentActiveChannel_ = channel;
      currentActiveChannel_->handleEvent(pollReturnTime_);
    }
    currentActiveChannel_ = NULL;
    eventHandling_ = false;
	
	//执行queueInLoop放入[等待队列]中的回调函数
    doPendingFunctors(); 
  }

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

3、与Channel相关的操作

对Channel的添加和删除操作,就是向EventLoop中添加/删除了对Channel事件的监听。

void updateChannel(Channel* channel); //在poller中添加或者更新通道
void removeChannel(Channel* channel); //从poller中删除通道
bool hasChannel(Channel* channel); //判断channel是否在poller中

4、与[定时器]相关的操作

// 在某个时刻
TimerId runAt(Timestamp time, TimerCallback cb); 
// 在一段时间,执行定时器回调函数cb
TimerId runAfter(double delay, TimerCallback cb);
// 每隔一段时间,执行定时器回调函数cb
TimerId runEvery(double interval, TimerCallback cb);
//取消定时器
void cancel(TimerId timerId);

5、runInLoop与queueInLoop

以上都是讲述了在创建EventLoop对象的线程下的操作,而runInLoop与queueInLoop引出了其他的线程。因此,要从这两个函数是否在IO线程中被调用,来分析它们的执行情况。

void EventLoop::runInLoop(Functor cb)
{
  //如果在[创建EventLoop对象的线程中]调用runInLoop,回调会同步进行;
  if (isInLoopThread())
  {
    cb(); 
  }
  else //如果在[`非`创建EventLoop对象的线程中]调用runInLoop
  {
	//cb会被加入队列,唤醒[创建EventLoop对象的线程]来调用这个Functor
    queueInLoop(std::move(cb));
  }
}

void EventLoop::queueInLoop(Functor cb)
{
  {
  MutexLockGuard lock(mutex_);
  //将cb放入pendingFunctors_队列
  pendingFunctors_.push_back(std::move(cb)); 
  }
  //调用queueInLoop的线程不是[创建EventLoop对象的线程] || 正在调用pending functors
  if (!isInLoopThread() || callingPendingFunctors_)
  {
    wakeup(); //使得IO线程执行pendingFunctors_队列中的回调函数
  }
}
(1) 分析runInLoop的执行情况

调用runInLoop()函数的执行情况根据是否在[创建EventLoop对象的线程中]调用runInLoop()分为两种:
case 1: [创建EventLoop对象的线程中]调用runInLoop(cb)
⇒ 同步地执行回调函数cb()
case2:不在[创建EventLoop对象的线程中]调用runInLoop(cb)
⇒ 转去调用queueInLoop()函数将cb放入pendingFunctors_队列
⇒ 执行wakeup()函数,导致activeChannels_可读事件被触发,进一步使loop()中的poll()函数返回,最后程序执行到doPendingFunctors()函数,即调用了pendingFunctors_队列中的回调函数

无论是不是当前IO线程调用了runInLoop(CallBack__)函数,CallBack__都会被回调。
① 如果是当前IO线程,则直接同步回调;
② 如果不是当前IO线程,则先将CallBack__添加到它的IO线程中的pendFunctors队列中,再唤醒它的IO线程去逐个执行pendFunctors队列中的函数

(2) 分析queueInLoop的执行情况

调用queueInLoop()函数的执行情况根据是否在[创建EventLoop对象的线程中]调用queueInLoop()也分为两种:
case 1:[创建EventLoop对象的线程中]调用queueInLoop(cb)
(1) 先将cb放入pendingFunctors_队列
(2) 执行下面代码,根据callingPendingFunctors_是否为true,判断是否调用wakeup

if (callingPendingFunctors_)
{
  wakeup(); 
}

case 2:不在[创建EventLoop对象的线程中]调用queueInLoop(cb)
先将cb放入pendingFunctors_队列 ==> 执行wakeup()

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值