Muduo分析及总结(五) EventLoop

一、EventLoop

one loop per thread,每个线程最多一个。
使用范例:

  muduo::InetAddress listenAddr(9981);
  muduo::EventLoop loop;

  muduo::TcpServer server(&loop, listenAddr);
  server.setConnectionCallback(onConnection);
  server.setMessageCallback(onMessage);
  if (argc > 1) {
    server.setThreadNum(atoi(argv[1]));
  }
  server.start();

  loop.loop();

作用:

  1. 开启一个loop循环,定期检测是否有活跃的通道事件,有则处理,无在不处理。
  2. loop循环中除了处理真实的通道事件,还会处理添加到Eventloop队列中的回调函数。
  3. 处理定时器事件。
  4. 更新通道到Poll中,或者移除Poller中的通道。
二、源码分析

Eventloop.h

class EventLoop : boost::noncopyable
{
 public:
  typedef boost::function<void()> Functor;

  EventLoop();
  ~EventLoop();  // force out-line dtor, for scoped_ptr members.

  ///
  /// Loops forever.
  ///
  /// Must be called in the same thread as creation of the object.
  ///
  void loop(); //开始loop

  void quit(); //退出loop

  ///
  /// Time when poll returns, usually means data arrivial.
  ///
  Timestamp pollReturnTime() const { return pollReturnTime_; }

  int64_t iteration() const { return iteration_; }

  /// Runs callback immediately in the loop thread.
  /// It wakes up the loop, and run the cb.
  /// If in the same loop thread, cb is run within the function.
  /// Safe to call from other threads.
  void runInLoop(const Functor& cb);
  /// Queues callback in the loop thread.
  /// Runs after finish pooling.
  /// Safe to call from other threads.
  void queueInLoop(const Functor& cb);

  // timers

  ///
  /// Runs callback at 'time'.
  /// Safe to call from other threads.
  ///
  TimerId runAt(const Timestamp& time, const TimerCallback& cb);
  ///
  /// Runs callback after @c delay seconds.
  /// Safe to call from other threads.
  ///
  TimerId runAfter(double delay, const TimerCallback& cb);
  ///
  /// Runs callback every @c interval seconds.
  /// Safe to call from other threads.
  ///
  TimerId runEvery(double interval, const TimerCallback& cb);
  ///
  /// Cancels the timer.
  /// Safe to call from other threads.
  ///
  void cancel(TimerId timerId);

  // internal usage
  void wakeup();
  void updateChannel(Channel* channel);
  void removeChannel(Channel* channel);

  // pid_t threadId() const { return threadId_; }
  void assertInLoopThread()
  {
	if (!isInLoopThread())
	{
	  abortNotInLoopThread();
	}
  }
  bool isInLoopThread() const { return threadId_ == CurrentThread::tid(); }
  // bool callingPendingFunctors() const { return callingPendingFunctors_; }
  bool eventHandling() const { return eventHandling_; }

  static EventLoop* getEventLoopOfCurrentThread();

 private:
  void abortNotInLoopThread();
  void handleRead();  // waked up
  void doPendingFunctors();

  //
  void printActiveChannels() const; // DEBUG  

  typedef std::vector<Channel*> ChannelList;

  bool looping_; /* atomic */    //当前是否在loop
  bool quit_; /* atomic */         //是否退出
  bool eventHandling_; /* atomic */  //当前是否在处理事件				
  bool callingPendingFunctors_; /* atomic */  //队列中是否有待处理函数
  int64_t iteration_;     			//FIXME ?
  const pid_t threadId_;  			//线程ID
  Timestamp pollReturnTime_; 	//Poll返回时间? 干什么用的?	
  boost::scoped_ptr<Poller> poller_;  //Poller方法
  boost::scoped_ptr<TimerQueue> timerQueue_;  //定时器队列
  int wakeupFd_;  //唤醒fd
  // unlike in TimerQueue, which is an internal class,
  // we don't expose Channel to client.
  boost::scoped_ptr<Channel> wakeupChannel_;  //唤醒通道
  ChannelList activeChannels_;        //活跃的通道列表
  Channel* currentActiveChannel_; //当前活跃的通道列表
  MutexLock mutex_;
  std::vector<Functor> pendingFunctors_; // @BuardedBy mutex_  //队列中待处理的方法
}

EventLoop.cc

namespace
{
__thread EventLoop* t_loopInThisThread = 0;

//Poll默认时间
const int kPollTimeMs = 10000;

//创建事件fd
int createEventfd()
{
  int evtfd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
  if (evtfd < 0)
  {
	LOG_SYSERR << "Failed in eventfd";
	abort();
  }
  return evtfd;
}

#pragma GCC diagnostic ignored "-Wold-style-cast"

//屏蔽SigPipe信号,防止中断退出
class IgnoreSigPipe
{
 public:
  IgnoreSigPipe()
  {
	::signal(SIGPIPE, SIG_IGN);
	LOG_TRACE << "Ignore SIGPIPE";
  }
};
#pragma GCC diagnostic error "-Wold-style-cast"

IgnoreSigPipe initObj;
}

EventLoop* EventLoop::getEventLoopOfCurrentThread()
{
  return t_loopInThisThread;
}

//EventLoop初始化时
//a、looping、quit_、eventHanding_、callingPendingFunctors_、iteration_默认初始化false
//b、设置当前线程ID。threadId_
//c、创建一个poll
//d、创建一个定时器队列
//e、创建一个唤醒事件fd
//f、创建一个唤醒事件通道
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_TRACE << "EventLoop created " << this << " in thread " << threadId_;

  //设置当前loop的地址,
  //若已有就不设置。
  if (t_loopInThisThread)
  {
	LOG_FATAL << "Another EventLoop " << t_loopInThisThread
			  << " exists in this thread " << threadId_;
  }
  else
  {
	t_loopInThisThread = this;
  }

  //设置唤醒通道的回调读,FIXME 这里设计应该有其它用途
  wakeupChannel_->setReadCallback(
	  boost::bind(&EventLoop::handleRead, this));
  // we are always reading the wakeupfd
  wakeupChannel_->enableReading();
}


//析构就是关闭句柄,
//将t_loopInThisThread置位空地址
EventLoop::~EventLoop()
{
  ::close(wakeupFd_);
  t_loopInThisThread = NULL;
}

//开始loop流程
void EventLoop::loop()
{
  assert(!looping_);
  assertInLoopThread();

  //设置下状态位
  looping_ = true;
  quit_ = false;
  LOG_TRACE << "EventLoop " << this << " start looping";

  while (!quit_)
  {
        //定期查询是否有活跃的通道
	activeChannels_.clear();
	pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);

	//FIXME_hqb  这个标志是做什么的?
	++iteration_;

	//日志级别开启时才会去循环输出打印
	//避免未开启对应日志级别情况下去执行
	//不必要的循环。
	if (Logger::logLevel() <= Logger::TRACE)
	{
	  printActiveChannels();
	}
	// TODO sort channel by priority

	//当前要处理下事件,置位状态。使用结果在下文的removeChannel()体现
	eventHandling_ = true;

	//若有活跃的通道,则处理下,并将当前时间也传给下层
	for (ChannelList::iterator it = activeChannels_.begin();
		it != activeChannels_.end(); ++it)
	{
	  currentActiveChannel_ = *it;
	  currentActiveChannel_->handleEvent(pollReturnTime_);
	}
	currentActiveChannel_ = NULL;
	eventHandling_ = false;

	//处理下队列中是否有需要执行的回调方法
	doPendingFunctors();
  }

  //记录下退出标志,并设置状态位
  LOG_TRACE << "EventLoop " << this << " stop looping";
  looping_ = false;
}

void EventLoop::quit()
{

  //loop退出
  //a、状态置位
  //b、如果在当前线程,就唤醒下fd
  quit_ = true;
  if (!isInLoopThread())
  {
	wakeup();
  }
}

void EventLoop::runInLoop(const Functor& cb)
{

  //在EventLoop中执行回调函数
  //a、如果Loop在自身当前线程,直接执行
  //b、如果Loop在别的线程,则将回调放到Loop的
  //   方法队列,保证回调在Loop所在的线程中执行   
  if (isInLoopThread())
  {
	cb();
  }
  else
  {
	queueInLoop(cb);
  }
}

void EventLoop::queueInLoop(const Functor& cb)
{
  //将回调函数放到待执行队列
  {
  MutexLockGuard lock(mutex_);
  pendingFunctors_.push_back(cb);
  }

  //唤醒条件参考下文总结。
  if (!isInLoopThread() || callingPendingFunctors_)
  {
	wakeup();
  }
}

//将执行回调添加到定时器队列
TimerId EventLoop::runAt(const Timestamp& time, const TimerCallback& cb)
{
  return timerQueue_->addTimer(cb, time, 0.0);
}

TimerId EventLoop::runAfter(double delay, const TimerCallback& cb)
{
  Timestamp time(addTime(Timestamp::now(), delay));
  return runAt(time, cb);
}

TimerId EventLoop::runEvery(double interval, const TimerCallback& cb)
{
  Timestamp time(addTime(Timestamp::now(), interval));
  return timerQueue_->addTimer(cb, time, interval);
}

//取消某个定时器事件
void EventLoop::cancel(TimerId timerId)
{
  return timerQueue_->cancel(timerId);
}

//更新通道
void EventLoop::updateChannel(Channel* channel)
{
  //Channel所在的Loop要和当前Loop保持一致
  assert(channel->ownerLoop() == this);

  //Loop所在线程不一致时,输出日志
  assertInLoopThread();

  //更新通道事件
  poller_->updateChannel(channel);
}


//移除通道
void EventLoop::removeChannel(Channel* channel)
{
  assert(channel->ownerLoop() == this);
  assertInLoopThread();
  if (eventHandling_)
  {
	//当要移除通道时,如果eventHandling_为true表示
	//正在Eventloop::loop()中,此时channel不能是正在
	//处理的currentActiveChannel,也不能再活跃的通道列表中
	//即activeChannels中。
	assert(currentActiveChannel_ == channel ||
		std::find(activeChannels_.begin(), activeChannels_.end(), channel) == activeChannels_.end());
  }

  //移除通道
  poller_->removeChannel(channel);
}

//
void EventLoop::abortNotInLoopThread()
{
  LOG_FATAL << "EventLoop::abortNotInLoopThread - EventLoop " << this
			<< " was created in threadId_ = " << threadId_
			<< ", current thread id = " <<	CurrentThread::tid();
}

//这里用到的一个设计就是添加了
//回调函数到队列,通过往fd写个
//标志来通知,让阻塞的Poll立马
//返回去执行回调函数。

//唤醒下,写一个8字节数据
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";
  }
}

//读下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";
  }
}

void EventLoop::doPendingFunctors()
{

  //处理队列中的回调函数
  std::vector<Functor> functors;

  //FIXME_hqb 状态标志的作用是什么
  callingPendingFunctors_ = true;

   //交换出来,避免调用时长时间占用锁
  {
  MutexLockGuard lock(mutex_);
  functors.swap(pendingFunctors_);
  }

  for (size_t i = 0; i < functors.size(); ++i)
  {
	functors[i]();
  }
  callingPendingFunctors_ = false;
}


//输出下活跃的通道对应的事件,用于调试
void EventLoop::printActiveChannels() const
{
  for (ChannelList::const_iterator it = activeChannels_.begin();
	  it != activeChannels_.end(); ++it)
  {
	const Channel* ch = *it;
	LOG_TRACE << "{" << ch->reventsToString() << "} ";
  }
}

三、关键点解析
1、唤醒方式

在EventLoop的IO线程内执行某个用户的任务回调,即

void EventLoop::runInLoop(const Functor& cb)

如果用户在当前IO线程调用这个函数,回调会同步执行,如果用户在其他线程调用runInLoop(),cb会被加入队列,IO线程会被唤醒来调用这个Functor。唤醒的方式一般有三种。

  1. pipe,使用fd[0] 为读端,fd[1]为写端,半双工。等待线程关注fd[0]的可读事件。
  2. socketpair,也有一对文件描述符,可用于双向通信,全双工。
  3. eventfd。eventfd是一个比pipe更高效的线程间事件通知机制,一方面它比pipe少用一个pipe
    descriptor,节省了资源。另一方面,eventfd的缓冲区管理也简单的多,全部“buffer"只有定长8bytes,
    不想pipe那样可能有不定长的真正buffer。
2、回调函数放在当前IO线程执行目的

可以不用锁的前提下,保证线程安全。

3、什么时候唤醒
void EventLoop::queueInLoop(const Functor& cb)
{
  {
  MutexLockGuard lock(mutex_);
  pendingFunctors_.push_back(cb);
  }

  if (!isInLoopThread() || callingPendingFunctors_)
  {
    wakeup();
  }
}

这里唤醒有两个条件:

  1. isInLoopThread(), 如果调用的queneInLoop的线程不是IO线程,那么唤醒。
  2. callingPendingFunctors_, 如果在IO线程调用queueInLoop(),而此时正在调用pending functor,那么唤醒也是必要的。

只有在IO线程的事件回调中调用queueInLoop(),才无须wakeup()。

4、不在handleRead()中执行doPendingFunctors()原因

添加回调函数到队列可以调用如下两种方法:

void EventLoop::runInLoop(const Functor& cb)
void EventLoop::queueInLoop(const Functor& cb)

如果 doPendingFunctors() 放在handleRead中执行,
1、如果用户调用的 EventLoop::runInLoop() 方法还好,会检测唤醒。
2、如果在当前IO线程,用户调用的是 EventLoop::queueInLoop() 回调函数不会立马被执行,需要等EventLoop::wakeup唤醒才会,这样就会存在很大的延迟。

备注:
参考链接 https://blog.csdn.net/FreeeLinux/article/details/53510541

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值