我已经建立了一个使用boost ASIO的C++库。该库需要既是线程安全的又是安全的。 它有服务调度程序线程,它调用io_service::run()
。为了支持fork安全,我已经注册了pre_fork,post_fork_parent和post_fork_child处理程序。 pre_fork()
处理程序,调用_io_service.notify_fork(boost::io_service:fork_prepare()
,post_fork_parent处理程序调用_io_service.notify_fork(boost::asio::io_service::fork_parent)
和post_fork_child调用_io_service.notify_fork(boost::asio::io_service::fork_child)
。如何使升压asio叉安全
我遇到的问题是,当fork()
发生时,服务调度程序线程可能处于某个操作的中间,并且可能已获取锁定对象的数据成员。因此,当我们调用_io_service.notify_fork(boost::asio::io_service::fork_child)
时,子进程会将它们视为同一状态并在post_fork_child()中尝试获取对同一对象的锁定,并因此无限期地被阻止(因为子节点中没有线程来释放解锁)。
堆栈跟踪我的子进程,这是阻断看,是 -
fffffd7ffed07577 lwp_park (0, 0, 0)
fffffd7ffecffc18 mutex_lock_internal() + 378
fffffd7ffecfffb2 mutex_lock_impl() + 112
fffffd7ffed0007b mutex_lock() + b
fffffd7fff26419d __1cFboostEasioGdetailLscoped_lock4n0CLposix_mutex__2t5B6Mrn0D__v_() + 1d
fffffd7fff2866a2 __1cFboostEasioGdetailQdev_poll_reactorMfork_service6Mn0BKio_serviceKfork_event__v_() + 32
fffffd7fff278527 __1cFboostEasioGdetailQservice_registryLnotify_fork6Mn0BKio_serviceKfork_event__v_() + 107
fffffd7fff27531c __1cDdesGtunnelQServiceSchedulerPpost_fork_child6M_v_() + 1c
fffffd7fff29de24 post_fork_child() + 84
fffffd7ffec92188 _postfork_child_handler() + 38
fffffd7ffecf917d fork() + 12d
fffffd7ffec172d5 fork() + 45
fffffd7ffef94309 fork() + 9
000000000043299d main() + 67d
0000000000424b2c ????????()
显然,“dev_poll_reactor”被锁定的(因为它似乎是派遣一些悬而未决的事件)的服务调度线程当叉子发生了这是造成问题的原因。
我觉得要解决这个问题,我需要确保服务调度线程没有处于任何处理过程中,当发生派生和一个办法可以保证将调用pre_fork io_service.stop()
()处理器但没有按听起来不是一个好的解决方案。请让我知道什么是正确的方法来使图书馆的叉子安全?
代码片段看起来像这样。
/**
* Combines Boost.ASIO with a thread for scheduling.
*/
class ServiceScheduler : private boost::noncopyable
{
public :
/// The actual thread used to perform work.
boost::shared_ptr<boost::thread> _service_thread;
/// Service used to manage async I/O events
boost::asio::io_service _io_service;
/// Work object to block the ioservice thread.
std::auto_ptr<boost::asio::io_service::work> _work;
...
};
/**
* CTOR
*/
ServiceScheduler::ServiceScheduler()
: _io_service(),
_work(std::auto_ptr<boost::asio::io_service::work>(
new boost::asio::io_service::work(_io_service))),
_is_running(false)
{
}
/**
* Starts a thread to run async I/O service to process the scheduled work.
*/
void ServiceScheduler::start()
{
ScopedLock scheduler_lock(_mutex);
if (!_is_running) {
_is_running = true;
_service_thread = boost::shared_ptr<boost::thread>(
new boost::thread(boost::bind(
&ServiceScheduler::processServiceWork, this)));
}
}
/**
* Processes work passed to the ASIO service and handles uncaught
* exceptions
*/
void ServiceScheduler::processServiceWork()
{
try {
_io_service.run();
}
catch (...) {
}
}
/**
* Pre-fork handler
*/
void ServiceScheduler::pre_fork()
{
_io_service.notify_fork(boost::asio::io_service::fork_prepare);
}
/**
* Post-fork parent handler
*/
void ServiceScheduler::post_fork_parent()
{
_io_service.notify_fork(boost::asio::io_service::fork_parent);
}
/**
* Post-fork child handler
*/
void ServiceScheduler::post_fork_child()
{
_io_service.notify_fork(boost::asio::io_service::fork_child);
}
我使用boost 1.47并在Solaris i386上运行应用程序。库和应用程序使用studio-12.0构建。
在调用fork之后,您是否期望在子中执行其他任何调用exec()或_exit()的操作?如果是这样,你应该重新考虑。如果不是,我没有看到问题。 – janm 2012-03-04 03:27:22
您可以保留主线程仅用于管理,命令界面任务和父子处理。在fork之后,只有主线程存在于子中。您可以持有用于恢复的内部配置数据,并在子进程中创建所需的线程。这样确保了一个干净的封装,并避免了锁定需求。 – 2015-04-17 08:51:06
在尝试为两个项目使用boost :: asio之后,我得出结论,最好不要使用boost。即使是简单的例子,它也会出现故障。其复杂的模板结构过于难以理解,并且不可能有意义地通过并确定可能的原因。 – wallyk 2015-06-29 19:04:37