繁体   English   中英

对于boost io_service,在epoll_wait上是否只有一个线程被阻塞?

[英]For boost io_service, is only-one thread blocked on epoll_wait?

我阅读了Boost ASIO的源代码,我想发现它只有一个线程可以调用epoll_wait(当然,如果我使用epoll反应器)。
我想找到关于多个线程调用epoll_wait的解决方案,这可能导致不同的线程同时对同一套接字进行读取。 我读了一些关键代码,如下所示:

// Prepare to execute first handler from queue.
      operation* o = op_queue_.front();
      op_queue_.pop();
      bool more_handlers = (!op_queue_.empty());

      if (o == &task_operation_)
      {
        task_interrupted_ = more_handlers;

        if (more_handlers && !one_thread_)
          wakeup_event_.unlock_and_signal_one(lock);
        else
          lock.unlock();

        task_cleanup on_exit = { this, &lock, &this_thread };
        (void)on_exit;

        // Run the task. May throw an exception. Only block if the operation
        // queue is empty and we're not polling, otherwise we want to return
        // as soon as possible.
        task_->run(!more_handlers, this_thread.private_op_queue);
      }

task_是epoll反应堆,它将在运行中调用epoll_wait,我猜可能只有一个线程可以调用它,因为op_queue_中只有一个“ task_operation_”,对吗?
如果我想在多线程中使用epoll,或者我可以使用“ EPOLLONESHOT”,以便它可以确保一个线程一次处理一个套接字。

只有一个线程将调用epoll_wait 一旦线程接收到描述符的事件通知,它将把描述符解复io_service运行io_service所有线程。 根据特定平台的实施说明

主题:

  • 使用epollepoll是在调用io_service::run()io_service::run_one()io_service::poll()io_service::poll_one()的线程之一中执行的。

单个描述符将由执行I / O的单个线程处理。 因此,当使用异步操作时,对于给定的套接字,不会同时执行I / O。

  • 第一种情况是使用单个io_service实例并从多个线程调用io_service::run方法时。

让我们看一下schduler::run函数(简化):

std::size_t scheduler::run(asio::error_code& ec)
{
  mutex::scoped_lock lock(mutex_);

  std::size_t n = 0;
  for (; do_run_one(lock, this_thread, ec); lock.lock())
    if (n != (std::numeric_limits<std::size_t>::max)())
      ++n;
  return n;
}

因此,在持有锁的情况下,它将调用do_run_one方法,该方法类似于:

std::size_t scheduler::do_run_one(mutex::scoped_lock& lock,
    scheduler::thread_info& this_thread,
    const asio::error_code& ec)
{
  while (!stopped_)
  {
    if (!op_queue_.empty())
    {
      // Prepare to execute first handler from queue.
      operation* o = op_queue_.front();
      op_queue_.pop();
      bool more_handlers = (!op_queue_.empty());

      if (o == &task_operation_)
      {
        task_interrupted_ = more_handlers;

        if (more_handlers && !one_thread_)
          wakeup_event_.unlock_and_signal_one(lock);
        else
          lock.unlock();

        task_cleanup on_exit = { this, &lock, &this_thread };
        (void)on_exit;

        task_->run(!more_handlers, this_thread.private_op_queue);
      }
      else
      {
        //......
      }
    }
    else
    {
      wakeup_event_.clear(lock);
      wakeup_event_.wait(lock);
    }
  }

  return 0;
}

代码中有趣的部分是这些行:

if (more_handlers && !one_thread_)
  wakeup_event_.unlock_and_signal_one(lock);
else
  lock.unlock(); 

我们现在讨论的情况是具有多个线程的情况,因此第一个条件将得到满足(假设在op_queue_中有许多待处理的任务)。

wakeup_event_.unlock_and_signal_one最终要做的是释放/解锁lock并通知正在有条件等待的线程之一。 因此,有了这个,至少另一个线程(无论谁获得了锁)都可以立即调用do_run_one

task_你的情况是epoll_reactor正如你所说。 并且,在它的run方法中,它调用epoll_wait (不持有schedulerlock_ )。

有趣的是,它遍历epoll_wait返回的所有就绪描述符时会epoll_wait 它将它们推回到它作为参数引用接收的操作队列中。 现在,已推送的操作的运行时类型为descriptor_state而不是task_operation_

for (int i = 0; i < num_events; ++i)
  {
    void* ptr = events[i].data.ptr;
    if (ptr == &interrupter_)
    {
      // don't call work_started() here. This still allows the scheduler to
      // stop if the only remaining operations are descriptor operations.
      descriptor_state* descriptor_data = static_cast<descriptor_state*>(ptr);
      descriptor_data->set_ready_events(events[i].events);
      ops.push(descriptor_data);
    }
  }

因此,在scheduler::do_run_one内部的while循环的下一次迭代中,对于完成的任务,它将到达else分支(我之前在粘贴中删除了该分支):

     else
      {
        std::size_t task_result = o->task_result_;

        if (more_handlers && !one_thread_)
          wake_one_thread_and_unlock(lock);
        else
          lock.unlock();

        // Ensure the count of outstanding work is decremented on block exit.
        work_cleanup on_exit = { this, &lock, &this_thread };
        (void)on_exit;

        // Complete the operation. May throw an exception. Deletes the object.
        o->complete(this, ec, task_result);

        return 1;
      }

哪个调用会调用complete函数指针,进而可能会调用用户传递的async_readasync_write API的句柄。

  • 第二种情况是,您创建io_service对象池并在1个或多个线程上调用其run方法,即io_servicethread之间的映射可能是1:1或1:N,这可能适合您的应用程序。 这样,您可以以循环方式将io_service对象分配给soucket对象。

现在,问您的问题:

如果我想在多线程中使用epoll,或者我可以使用“ EPOLLONESHOT”,以便它可以确保一个线程一次处理一个套接字。

如果我正确理解了这一点,则想使用1个线程将所有事件处理到一个套接字。 我认为可以通过遵循方法2(即创建io_service对象池并将其映射到1个线程)来实现。 这样,您可以确保特定套接字上的所有活动将仅由一个线程(即io_service:run的线程)寻址。

您不必担心在上述情况下设置EPOLLONESHOT

我不确定使用第一种方法是多线程和1 io_service得到相同的行为。

但是,如果您根本不使用线程,即io_service在单个线程上运行,那么您不必担心所有这些,毕竟asio的目的是抽象所有这些内容。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM