2012-02-20 147 views
21

我已經建立了一個使用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構建。

+0

在調用fork之後,您是否期望在子中執行其他任何調用exec()或_exit()的操作?如果是這樣,你應該重新考慮。如果不是,我沒有看到問題。 – janm 2012-03-04 03:27:22

+0

您可以保留主線程僅用於管理,命令界面任務和父子處理。在fork之後,只有主線程存在於子中。您可以持有用於恢復的內部配置數據,並在子進程中創建所需的線程。這樣確保了一個乾淨的封裝,並避免了鎖定需求。 – 2015-04-17 08:51:06

+3

在嘗試爲兩個項目使用boost :: asio之後,我得出結論,最好不要使用boost。即使是簡單的例子,它也會出現故障。其複雜的模板結構過於難以理解,並且不可能有意義地通過並確定可能的原因。 – wallyk 2015-06-29 19:04:37

回答

2

asio代碼指定在io_service代碼中存在任何代碼時notify_fork()不起作用。

此函數不能被調用,而任何其他io_service對象函數,或 與io_service對象相關聯的I/O對象的任何功能,正在 稱爲在另一個線程。但是,在完成處理程序中從 調用此函數是安全的,前提是沒有其他線程正在訪問io_service的 。

這似乎包括run或任何與該庫相關聯的IO。我認爲你的pre_fork處理,應該重置一個工作項目。

例如從boost documentation

boost::asio::io_service io_service; 
auto_ptr<boost::asio::io_service::work> work(
    new boost::asio::io_service::work(io_service)); 
... 
pre_fork() { 
    work.reset(); // Allow run() to exit. 
    // check run has finished... 
    io_service.notify_fork(...); 
} 

護理仍需要採取

  1. 確保run()post_fork()完成之前不叫。
  2. 確保新work對象被用於下一個run
  3. 正確同步創建以確保run終止點樣。
0

您可以使用io_service :: run_one檢查叉是否已安排/ io_service仍應該運行。當發生分叉時,可以將一些工作添加到io_service中,以使線程喚醒。線程檢查運行情況並立即停止。發生fork之後,父或子可以重新啓動工作線程。

/** 
* 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; 
    ServiceScheduler(); 
    void start(); 
    void pre_fork(); 
private: 
    void processServiceWork(); 
    void post_fork_parent(); 
    void post_fork_child(); 
    std::atomic<bool> _is_running; 
}; 

/** 
* 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() 
{ 
    if(!_is_running) { 
     _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 { 
     while(_is_running) { 
      _io_service.run_one(); 
     } 
    } 
    catch (...) { 
    } 
    _is_running = false; 
} 

/** 
* Pre-fork handler 
*/ 
void ServiceScheduler::pre_fork() 
{ 
    _is_running = false; 
    _io_service.post([](){ /*no_op*/}); 
    _service_thread->join(); 
    _service_thread.reset(); 
    _io_service.notify_fork(boost::asio::io_service::fork_prepare); 
} 

/** 
* Post-fork parent handler 
*/ 
void ServiceScheduler::post_fork_parent() 
{ 
    start(); 
    _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); 
}