muduo库net源码分析四(线程唤醒)

本文介绍了Linux线程间通信的三种方法:pipe、socketpair和eventfd,重点讲解了eventfd的使用,以及在事件驱动编程中EventLoop的实现。EventLoop通过eventfd进行线程唤醒,确保线程间高效的通知机制。同时,展示了runInLoop和queueInLoop函数,如何在IO线程中安全地执行回调函数。
摘要由CSDN通过智能技术生成

线程唤醒方法

一个线程如何通知另一个等待中的线程,可以有三个方法:

1、pipe 管道有一对文件描述符。pipe[0] 对应管道的读端,pipe[1] 对应管道的写端,等待的线程关注pipe[0]的读端,通知线程向pipe[1] 写入数据,pipe[0] 就变的可读了,等待线程获得通知就唤醒线程。

2、socketpair 和管道一样有一对文件描述符,不同的是可以双向通信。

3、eventfd。等待线程监听文件描述符可读事件,通知线程只要往这个线程写入数据,等待线程就会获得通知。是一个比 pipe 更高效的线程间事件通知机制,一方面它比 pipe 少用一个 file descripor,节省了资源;另一方面,eventfd 的缓冲区管理也简单得多,全部“buffer” 只有定长8 bytes,不像 pipe 那样可能有不定长的真正 buffer。

以上方法可以用在进程和线程,线程还可以用条件变量。不同的是上面三种都可以统一纳入IO复用来管理,而条件变量不行。

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),
    threadId_(CurrentThread::tid()),
    poller_(Poller::newDefaultPoller(this)),
    timerQueue_(new TimerQueue(this)),
    wakeupFd_(createEventfd()),  // 创建eventfd
    wakeupChannel_(new Channel(this, wakeupFd_)), // eventfd 的channel
    currentActiveChannel_(NULL)
{
  LOG_TRACE << "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_->setReadCallback(
      boost::bind(&EventLoop::handleRead, this));
  // we are always reading the wakeupfd
  wakeupChannel_->enableReading();
}

// 唤醒
void EventLoop::wakeup()
{
  uint64_t one = 1;
  //ssize_t n = sockets::write(wakeupFd_, &one, sizeof one);
  ssize_t n = ::write(wakeupFd_, &one, sizeof one);
  if (n != sizeof one)
  {
    LOG_ERROR << "EventLoop::wakeup() writes " << n << " bytes instead of 8";
  }
}

// eventfd 事件处理函数
void EventLoop::handleRead()
{
  uint64_t one = 1;
  //ssize_t n = sockets::read(wakeupFd_, &one, sizeof one);
  ssize_t n = ::read(wakeupFd_, &one, sizeof one);
  if (n != sizeof one)
  {
    LOG_ERROR << "EventLoop::handleRead() reads " << n << " bytes instead of 8";
  }
}

EventLoop中非常有用的一个函数runInLoop

在IO线程中执行某个回调函数,改函数可以跨线程调用

// 在I/O线程中执行某个回调函数,该函数可以跨线程调用
void EventLoop::runInLoop(const Functor& cb)
{
  if (isInLoopThread())
  {
    // 如果是当前IO线程调用runInLoop,则同步调用cb
    cb();
  }
  else
  {
    // 如果是其它线程调用runInLoop,则异步地将cb添加到队列
    // 以便让EventLoop 所在的线程执行这个回调函数
    queueInLoop(cb);
  }
}

 

void EventLoop::queueInLoop(const Functor& cb)
{
  {
  MutexLockGuard lock(mutex_);
  pendingFunctors_.push_back(cb);
  }
  // 调用queueInLoop的线程不是当前IO线程需要唤醒
  // 或者调用queueInLoop的线程是当前IO线程,并且此时正在调用pending functor,需要唤醒
  // 只有当前IO线程的事件回调中调用queueInLoop才不需要唤醒
  if (!isInLoopThread() || callingPendingFunctors_)
  {
    wakeup();
  }
}
while (!quit_)
  {
    activeChannels_.clear();
    pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);
    if (Logger::logLevel() <= Logger::TRACE)
    {
      printActiveChannels();
    }
    eventHandling_ = true;
    for (ChannelList::iterator it = activeChannels_.begin();
        it != activeChannels_.end(); ++it)
    {
      currentActiveChannel_ = *it;
      currentActiveChannel_->handleEvent(pollReturnTime_);
    }
    currentActiveChannel_ = NULL;
    eventHandling_ = false;
    // 让IO线程也能执行一些计算任务,IO不忙的时候,处于阻塞状态
    doPendingFunctors();  // 其他线程或者本线程添加的一些回调任务
  }
void EventLoop::doPendingFunctors()
{
  std::vector<Functor> functors;
  callingPendingFunctors_ = true;

  {
  MutexLockGuard lock(mutex_);
  functors.swap(pendingFunctors_);
  }

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

不是简单地在临界区内依次调用Functor,而是把回调列表swap到functors中,这样一方面减小了临界区的长度(意味着不会阻塞其它线程的queueInLoop()),另一方面,也避免了死锁(因为Functor可能再次调用queueInLoop())。

由于doPendingFunctors()调用的Functor可能再次调用queueInLoop(cb),这时,queueInLoop()就必须wakeup(),否则新增的cb可能就不能及时调用了。

muduo没有反复执行doPendingFunctors()直到pendingFunctors为空,这是有意的,否则IO线程可能陷入死循环,无法处理IO事件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值