【发布时间】:2014-02-17 17:39:28
【问题描述】:
我在 OS X 上遇到了 Boost Asio 的问题,其中 io_service 析构函数有时无限期挂起。我有一个相对简单的复制案例:
#include <boost/asio.hpp>
#include <boost/thread.hpp>
int main(int argc, char* argv[]) {
timeval tv;
gettimeofday(&tv, 0);
std::time_t t = tv.tv_sec;
std::tm curr;
// The call to gmtime_r _seems_ innocent, but I cannot reproduce without this
std::tm* curr_ptr = gmtime_r(&t, &curr);
{
boost::asio::io_service ioService;
boost::asio::deadline_timer timer(ioService);
ioService.post([&](){
// This will also call gmtime_r, but just calling that is not enough
timer.expires_from_now(boost::posix_time::milliseconds(1));
timer.async_wait([](const boost::system::error_code &) {});
});
ioService.post([&](){
ioService.post([&](){});
});
// Run some threads
boost::thread_group workers;
for (auto i=0; i<3; ++i) {
workers.create_thread([&](){ ioService.run(); });
}
workers.join_all();
} // hangs here in the io_service destructor
return 0;
}
基本上,这只是在队列中发布两个处理程序,其中一个调度一个计时器,另一个只是发布另一个处理程序。有时这个简单的程序会导致io_service 析构函数无限期挂起,特别是在kqueue_reactor 析构过程中的pipe_select_interrupter 析构函数中。这会在管道读取描述符上的系统调用 close() 中阻塞。
为了触发错误,我使用 shell 脚本在循环中调用程序(但也可以在上面的示例中使用循环触发):
#!/bin/csh
set yname="foo"
while ( $yname != "" )
date
./hangtest
end
如果出现以下情况,我将无法再复制:
- 删除开头的
gmtime_r()调用 (!)。编辑:这似乎只适用于我使用脚本运行。如果我改为在程序本身中添加一个循环,我也可以在没有调用的情况下重现它,根据 ruslo 的评论。 - 删除对处理程序中计时器上
async_wait()的调用,或将计时器设置移到处理程序之外。 - 删除第二个处理程序中的
post()。 - 减少线程数。
- 在
kqueue_reactor::interrupt()中放置一个互斥锁。此函数从async_wait()和post()调用,并使用无法关闭的读取描述符调用kevent()。
我在上面的代码中做错了吗?
我在 OS X 10.8.5 上运行 Boost 1.54 并使用 clang -stdlib=libc++ -std=c++11 进行编译。我还可以使用 Boost 1.55 中的 Boost Asio 进行复制(Boost 1.54 的其余部分保持原样)。
编辑:我也可以在 OS X 10.9.1 上重现(使用相同的可执行文件)。
【问题讨论】:
-
已确认。我尝试使用循环(一次性验证)且没有 gmtime_r 的简化版本:link
-
@ruslo:太棒了,谢谢 - 到目前为止,如果没有 gmtime_r 调用,我无法重现,但我会再试一次。
-
@ruslo:有趣的是,如果我使用脚本运行(我运行了大约一个小时),没有 gmtime_r 调用就无法重现。但是,如果我像您的示例一样使用 for 循环运行,即使没有 gmtime_r 调用,我也能够重现它。
标签: c++ multithreading macos boost boost-asio