我对muduo c++网络库的理解(一) - 木东驿站 - Powered by MoodBlog

CONTENT

我对muduo c++网络库的理解(一)

muduo网络库

muduo(木铎)是陈硕巨神在业余时间(特喵的好强!)编写的一套网络支持库,目前只支持linux平台,最新版本提供了对ipv6的支持。

muduo线程模型采用one loop per thread + thread pool,即每个线程拥有一个事件循环,这些线程会提前在线程池准备好,等待用户往里面添加任务。这种线程模型使用reactor模式实现。

muduo源代码:https://github.com/chenshuo/muduo

muduo中的reactor模式

muduo使用一个主线程和可选的线程池来完成任务,主线程用来监听新的socket连接。

如果线程池中的线程大于0,在就把新连接的处理任务均衡的放置到其中一个线程。

如果只有主线程,就把连接处理放到主线程中,和监听任务共用一个线程。

每个线程(包括主线程)都含有一个eventloop,它负责当前线程中各个连接的io事件以及各种逻辑处理。eventloop使用io复用(epoll、poll或select)进行对io事件的监听,一旦有新的事件到来io复用函数就立刻返回,eventloop调用这些事件相应的处理器来处理。另外eventloop中还有一个方法队列functors,我们可以跨线程的往里面添加新的方法,这些方法会在处理完io事件后执行。

io事件除了网络io,还包括eventfd(用于唤醒),timerfd(计时器任务)

eventloop核心代码如下:

  while (!quit_)
  {
    activeChannels_.clear();
    
    //等待io复用函数返回
    pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);
    ++iteration_;
    if (Logger::logLevel() <= Logger::TRACE)
    {
      printActiveChannels();
    }
    // 处理IO事件
    eventHandling_ = true;
    for (Channel* channel : activeChannels_)
    {
      currentActiveChannel_ = channel;
      currentActiveChannel_->handleEvent(pollReturnTime_);
    }
    currentActiveChannel_ = NULL;
    eventHandling_ = false;
    
    //执行方法队列中的方法
    doPendingFunctors();
  }

方法队列

增加方法:

void EventLoop::runInLoop(Functor cb)
{
  if (isInLoopThread())
  {
    cb();
  }
  else
  {
    queueInLoop(std::move(cb));
  }
}

void EventLoop::queueInLoop(Functor cb)
{
  {
      MutexLockGuard lock(mutex_);
      pendingFunctors_.push_back(std::move(cb));
  }

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

这里提供了两个方法,这是为了区分两种情况:

(1)在eventloop所属线程给该eventloop方法队列添加方法。

(2)在其它线程给该eventloop方法队列添加方法。

之所以设定方法队列,而不是直接运行,是因为我们想指定某某方法在特定的某个eventloop中执行。

对于第一种情况,我们已经处于目标线程及目的eventloop中,我们直接运行该方法即可,不用费时费力的添加到队列中,而且这样可以直接运行,没有任何延迟。

对于第二种情况,当前线程不是我们预期的线程,所以要先把方法添加到目的eventloop中,让目标线程的eventloop后续自己运行这个方法。

runInloop的作用就是判断这两种情况,选择不同的方式去执行我们的方法。

queueInLoop的作用是把方法添加到队列中,因为该方法会出现在多个线程中,所以对方法队列的push_back操作加锁。c++中的std::function支持移动初始化,所以这里用move提升性能。(减少一次拷贝)

最后那个判断是为了解决这个问题:如果io事件不多,eventloop大部分时间都阻塞在poll,那么可能需要很久我们的方法才有机会执行,毕竟方法的执行是eventloop循环的最后一个任务。

wakeup()方法是这样解决上述问题的:提前给poll注册一个专属的eventfd(linux新版特性),然后在wakeup中给这个fd随便写入一点东西,然后poll就会返回了。这样eventloop有机会执行后面的方法队列。

一开始我觉得判断isInLoopThread有些多余,既然都执行queueInLoop了,难道不是一定不在当前线程嘛。后来我发现即使是本线程,放置某些任务时也不能立刻执行,比如forceclose强制关闭连接,如果立刻执行,后面本次要执行的方法就无法使用该连接了,所以要放到方法队列中下次执行。

callingpendingFunctors是确定当前是不是正在执行方法队列,如果在执行就得wakeup唤醒下次poll调用。

如果不在执行,那么此时一定在处理io事件,poll过程已经过去了,所以这里不需要唤醒。

线程池的启动

在服务器start方法中,有这样一句代码

threadPool_->start(threadInitCallback_);

threadInitCallback是用户定义的回调函数,可以为空。

线程池start代码如下:

void EventLoopThreadPool::start(const ThreadInitCallback& cb)
{
    assert(!started_);
    baseLoop_->assertInLoopThread();

    started_ = true;

    for (int i = 0; i < numThreads_; ++i)
    {
        char buf[name_.size() + 32];
        snprintf(buf, sizeof buf, "%s%d", name_.c_str(), i);
        EventLoopThread* t = new EventLoopThread(cb, buf);
        threads_.push_back(std::unique_ptr<EventLoopThread>(t));
        loops_.push_back(t->startLoop());
    }
    if (numThreads_ == 0 && cb)
    {
        cb(baseLoop_);
    }
}

这段代码创建出numThreads个EventLoopThread,然后将其封装为unique_ptr放入threads_集合中。

在loops中插入新线程中eventloop的指针,该指针由EventLoopThread::startLoop()返回

EventLoop* EventLoopThread::startLoop()
{
  assert(!thread_.started());
  thread_.start();

  EventLoop* loop = NULL;
  {
    MutexLockGuard lock(mutex_);
    while (loop_ == NULL)
    {
      cond_.wait();
    }
    loop = loop_;
  }

  return loop;
}

这段代码使用了环境变量,很明显在等待loop不为空这个条件。那么loop是在哪里创建的呢?

其中有一句thread_.start(),这一句是启动新线程,然后新线程中的方法就开始执行了。

void EventLoopThread::threadFunc()
{
  EventLoop loop;

  if (callback_)
  {
    callback_(&loop);
  }

  {
    MutexLockGuard lock(mutex_);
    loop_ = &loop;
    cond_.notify();
  }

  loop.loop();
  //assert(exiting_);
  MutexLockGuard lock(mutex_);
  loop_ = NULL;
}

loop是在新线程中创建的,其实很好理解,因为后面我们需要判断某loop的所属线程,所以要在新线程中创建loop,这样创建时就可以指定loop所在的线程,并记录其线程id。我觉得还有一种做法就是在主线程中创建eventloop,然后在线程方法中为loop的线程id赋值,这样就无需条件变量参与了。



个快快 2018年11月12日 天气 晴

REMARKS

© 2018 MoodBlog 0.2 个快快 作品 | 参考主题: mathilda by fuzzz. | 鲁ICP备16047814号