【问题标题】:Only thread handling io_service is waiting even though async I/O operations are pending即使异步 I/O 操作处于挂起状态,也只有线程处理 io_service 正在等待
【发布时间】:2013-03-31 20:41:30
【问题描述】:

Boost 的 ASIO 调度程序似乎有一个严重的问题,我似乎找不到解决方法。症状是,唯一等待调度的线程留在pthread_cond_wait f 中,尽管有待处理的 I/O 操作需要它阻塞在 epoll_wait 中。

我可以通过在一个循环中让一个线程调用poll_one 直到它返回零来最容易地复制这个问题。这会使调用run 的线程卡在pthread_cond_wait 中,而调用poll_one 的线程会跳出循环。据推测,io_service 期望该线程在epoll_wait 中返回阻塞,但它没有义务这样做,而且这种期望似乎是致命的。

是否要求线程与io_services 静态关联?

这是一个显示死锁的示例。这是处理此 io_service 的唯一线程,因为其他线程已继续。肯定有待处理的套接字操作:

#0 pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1 boost::asio::detail::posix_event::wait<boost::asio::detail::scoped_lock<boost::asio::detail::posix_mutex> > (...) at /usr/include/boost/asio/detail/posix_event.hpp:80
#2 boost::asio::detail::task_io_service::do_run_one (...) at /usr/include/boost/asio/detail/impl/task_io_service.ipp:405
#3 boost::asio::detail::task_io_service::run (...) at /usr/include/boost/asio/detail/impl/task_io_service.ipp:146

我认为错误如下:如果为 I/O 队列提供服务的线程是在 I/O 套接字就绪检查上阻塞的线程,并且它调用调度函数,如果有任何其他线程阻塞在io 服务,它必须发出信号。它目前仅在当时有准备好运行的处理程序时发出信号。但这不会留下线程检查套接字准备情况。

【问题讨论】:

  • run_one() 的返回码是什么?
  • 如果返回1是正常的,只有返回0时才需要重置io_service。听起来您没有做错任何事情,您可以发布一个 sscce 吗?
  • @DavidSchwartz 你确定 pthread_cond_timedwait 调用来自 asio 吗?我在代码中看到了一些问题。
  • @DavidSchwartz 好的。你有一个或多个 io_service 实例吗?
  • 已提交bug and fix

标签: linux multithreading boost-asio


【解决方案1】:

这是一个错误。我已经能够通过在task_io_service::do_poll_one 的非关键部分添加延迟来复制它。这是booost/asio/detail/impl/task_io_service.ipp 中修改后的task_io_service::do_poll_one() 的sn-p。唯一添加的行是 sleep。

std::size_t task_io_service::do_poll_one(mutex::scoped_lock& lock,
    task_io_service::thread_info& this_thread,
    const boost::system::error_code& ec)
{
  if (stopped_)
    return 0;

  operation* o = op_queue_.front();
  if (o == &task_operation_)
  {
    op_queue_.pop();
    lock.unlock();

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

      // 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(false, this_thread.private_op_queue);
      boost::this_thread::sleep_for(boost::chrono::seconds(3));
    }

    o = op_queue_.front();
    if (o == &task_operation_)
      return 0;
  }

...

我的测试驱动程序相当基础:

  • 通过计时器的异步工作循环将打印“.”每 3 秒一次。
  • 产生一个将轮询io_service 的线程。
  • 延迟以允许新线程有时间轮询io_service,并在轮询线程在task_io_service::do_poll_one() 中休眠时进行主调用io_service::run()

测试代码:

#include <iostream>

#include <boost/asio/io_service.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/chrono.hpp>
#include <boost/thread.hpp>

boost::asio::io_service io_service;
boost::asio::steady_timer timer(io_service);

void arm_timer()
{
  std::cout << ".";
  std::cout.flush();
  timer.expires_from_now(boost::chrono::seconds(3));
  timer.async_wait(boost::bind(&arm_timer));
}

int main()
{
  // Add asynchronous work loop.
  arm_timer();

  // Spawn poll thread.
  boost::thread poll_thread(
    boost::bind(&boost::asio::io_service::poll, boost::ref(io_service)));

  // Give time for poll thread service reactor.
  boost::this_thread::sleep_for(boost::chrono::seconds(1));

  io_service.run();
}

还有调试:

[twsansbury@localhost 错误]$ gdb a.out
...
(gdb) r
启动程序:/home/twsansbury/dev/bug/a.out

[启用使用 libthread_db 进行线程调试]
.[新线程 0xb7feeb90 (LWP 31892)]
[线程 0xb7feeb90 (LWP 31892) 已退出]

此时,arm_timer() 已打印“.”一次(当它最初武装时)。轮询线程以非阻塞方式为反应器提供服务,并在op_queue_ 为空时休眠了 3 秒(task_operation_ 将在task_cleanup c 退出范围时添加回op_queue_)。当op_queue_ 为空时,主线程调用io_service::run(),发现op_queue_ 为空,并将其自身设为first_idle_thread_,并在其上等待wakeup_event。轮询线程完成休眠,返回0,让主线程等待wakeup_event

在等待 10~ 秒后,arm_timer() 有足够的时间准备好,我中断了调试器:

程序接收信号 SIGINT,中断。
__kernel_vsyscall()中的0x00919402
(gdb) BT
#0 0x00919402 在 __kernel_vsyscall ()
#1 0x0081bbc5 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib/libpthread.so.0
#2 0x00763b3d 在来自 /lib/libc.so.6 的 pthread_cond_wait@@GLIBC_2.3.2 ()
#3 0x08059dc2 in void boost::asio::detail::posix_event::wait >(boost::asio::detail::scoped_lock&) ()
#4 0x0805a009 in boost::asio::detail::task_io_service::do_run_one(boost::asio::detail::scoped_lock&, boost::asio::detail::task_io_service_thread_info&, boost::system::error_code const&) ( )
#5 0x0805a11c 在 boost::asio::detail::task_io_service::run(boost::system::error_code&) ()
#6 0x0805a1e2 in boost::asio::io_service::run() ()
#7 0x0804db78 in main()

并排时间表如下:

 投票线程 |主线程
----------------------------------------------------+--------- -----------------------------------------
  锁() |
  do_poll_one() |
  |-- 从 | 弹出任务操作_
  | queue_op_ |
  |-- 解锁() |锁()
  |-- 创建任务清理 | do_run_one()
  |-- 服务反应堆(非阻塞) | `-- queue_op_ 为空
  |-- ~task_cleanup() | |-- 设置线程为空闲
  | |-- 锁() | `--解锁()
  | `-- queue_op_.push( |
  |任务操作_) |
  `-- task_operation_ 是 |
      queue_op_.front() |
      `-- 返回 0 | // 仍在等待 wakeup_event
  解锁()|

据我所知,修补没有副作用:

if (o == &task_operation_)
  return 0;

到:

if (o == &task_operation_)
{
  if (!one_thread_)
    wake_one_thread_and_unlock(lock);
  return 0;
}

不管怎样,我已经提交了bug and fix。考虑密切关注官方回应的票证。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-30
    • 2013-03-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多