2016-12-14 119 views
2

我正在寫一個包含C++模塊(.so,使用boost.python)的python程序。
我開始運行C++函數的幾個python線程。boost.python C++多線程

這是C++代碼的樣子:

#include <boost/python.hpp> 
using namespace boost; 
void f(){ 
    // long calculation 

    // call python function 

    // long calculation 
} 

BOOST_PYTHON_MODULE(test) 
{ 
    python::def("f", &f); 
} 

而Python代碼:

from test import f 
t1 = threading.Thread(target=f) 
t1.setDaemon(True) 
t1.start() 
print "Still running!" 

我遇到一個問題: 「仍在運行的」消息沒有顯示,我發現C++線程持有GIL。

在我使用python代碼運行C++代碼的情況下,處理GIL的最佳方法是什麼?

謝謝! 加

+0

我不知道的boost ::蟒蛇(但感謝提的名字,它看起來很有趣),但這個答案看起來像它可能會解決你的問題:['結構no_gil'(http://stackoverflow.com/a/18648366/416224)。 – kay

+0

謝謝。我看到了那些saveThread/restoreThread方法,但我仍然需要重新調用函數中的Gil來調用一些Python代碼。 –

回答

1

我常常發現,使用RAII-style類來管理Global Interpreter Lock(GIL)提供了一個優雅異常安全的解決方案。

例如,對於以下with_gil類,當創建一個with_gil對象時,調用線程將獲取GIL。當with_gil對象被破壞時,它恢復GIL狀態。

/// @brief Guard that will acquire the GIL upon construction, and 
///  restore its state upon destruction. 
class with_gil 
{ 
public: 
    with_gil() { state_ = PyGILState_Ensure(); } 
    ~with_gil() { PyGILState_Release(state_); } 

    with_gil(const with_gil&)   = delete; 
    with_gil& operator=(const with_gil&) = delete; 
private: 
    PyGILState_STATE state_; 
}; 

和互補without_gil類則正好相反:

/// @brief Guard that will unlock the GIL upon construction, and 
///  restore its staet upon destruction. 
class without_gil 
{ 
public: 
    without_gil() { state_ = PyEval_SaveThread(); } 
    ~without_gil() { PyEval_RestoreThread(state_); } 

    without_gil(const without_gil&)   = delete; 
    without_gil& operator=(const without_gil&) = delete; 
private: 
    PyThreadState* state_; 
}; 

他們的函數內使用可以如下:

void f() 
{ 
    without_gil no_gil;  // release gil 
    // long calculation 
    ... 

    { 
    with_gil gil;   // acquire gil 
    // call python function 
    ... 
    }       // restore gil (release) 

    // long calculation 
    ... 
}       // restore gil (acquire) 

一個也可以使用更高水平的方便類提供類似std::lock_guard的體驗。 GIL的獲取和釋放,保存和恢復語義與普通互斥體略有不同。因此,gil_guard接口不同的是:

  • gil_guard.acquire()將收購GIL
  • gil_guard.release()將釋放GIL
  • gil_guard_restore()將恢復到以前的狀態
/// @brief Guard that provides higher-level GIL controls. 
class gil_guard 
{ 
public: 
    struct no_acquire_t {} // tag type used for gil acquire strategy 
    static no_acquire; 

    gil_guard()    { acquire(); } 
    gil_guard(no_acquire_t) { release(); } 
    ~gil_guard()   { while (!stack_.empty()) { restore(); } } 

    void acquire()   { stack_.emplace(new with_gil); } 
    void release()   { stack_.emplace(new without_gil); } 
    void restore()   { stack_.pop(); } 

    static bool owns_gil() 
    { 
    // For Python 3.4+, one can use `PyGILState_Check()`. 
    return _PyThreadState_Current == PyGILState_GetThisThreadState(); 
    } 

    gil_guard(const gil_guard&)   = delete; 
    gil_guard& operator=(const gil_guard&) = delete; 

private: 
    // Use std::shared_ptr<void> for type erasure. 
    std::stack<std::shared_ptr<void>> stack_; 
}; 

而且其用法是:

void f() 
{ 
    gil_guard gil(gil_guard::no_acquire); // release gil 
    // long calculation 
    ... 

    gil.acquire();      // acquire gil 
    // call python function 
    ... 
    gil.restore();      // restore gil (release) 

    // long calculation 
    ... 
}          // restore gil (acquire) 

下面是一個完整的例子demonstrating GIL管理這些輔助類:

#include <cassert> 
#include <iostream> // std::cout, std::endl 
#include <memory> // std::shared_ptr 
#include <thread> // std::this_thread 
#include <stack> // std::stack 
#include <boost/python.hpp> 

/// @brief Guard that will acquire the GIL upon construction, and 
///  restore its state upon destruction. 
class with_gil 
{ 
public: 
    with_gil() { state_ = PyGILState_Ensure(); } 
    ~with_gil() { PyGILState_Release(state_); } 

    with_gil(const with_gil&)   = delete; 
    with_gil& operator=(const with_gil&) = delete; 
private: 
    PyGILState_STATE state_; 
}; 

/// @brief Guard that will unlock the GIL upon construction, and 
///  restore its staet upon destruction. 
class without_gil 
{ 
public: 
    without_gil() { state_ = PyEval_SaveThread(); } 
    ~without_gil() { PyEval_RestoreThread(state_); } 

    without_gil(const without_gil&)   = delete; 
    without_gil& operator=(const without_gil&) = delete; 
private: 
    PyThreadState* state_; 
}; 

/// @brief Guard that provides higher-level GIL controls. 
class gil_guard 
{ 
public: 
    struct no_acquire_t {} // tag type used for gil acquire strategy 
    static no_acquire; 

    gil_guard()    { acquire(); } 
    gil_guard(no_acquire_t) { release(); } 
    ~gil_guard()   { while (!stack_.empty()) { restore(); } } 

    void acquire()   { stack_.emplace(new with_gil); } 
    void release()   { stack_.emplace(new without_gil); } 
    void restore()   { stack_.pop(); } 

    static bool owns_gil() 
    { 
    // For Python 3.4+, one can use `PyGILState_Check()`. 
    return _PyThreadState_Current == PyGILState_GetThisThreadState(); 
    } 

    gil_guard(const gil_guard&)   = delete; 
    gil_guard& operator=(const gil_guard&) = delete; 

private: 
    // Use std::shared_ptr<void> for type erasure. 
    std::stack<std::shared_ptr<void>> stack_; 
}; 

void f() 
{ 
    std::cout << "in f()" << std::endl; 

    // long calculation 
    gil_guard gil(gil_guard::no_acquire); 
    assert(!gil.owns_gil()); 
    std::this_thread::sleep_for(std::chrono::milliseconds(500)); 
    std::cout << "calculating without gil..." << std::endl; 

    // call python function 
    gil.acquire(); 
    assert(gil.owns_gil()); 
    namespace python = boost::python; 
    python::object print = 
    python::import("__main__").attr("__builtins__").attr("print"); 
    print(python::str("calling a python function")); 
    gil.restore(); 

    // long calculation 
    assert(!gil.owns_gil()); 
    std::cout << "calculating without gil..." << std::endl; 
} 

BOOST_PYTHON_MODULE(example) 
{ 
    // Force the GIL to be created and initialized. The current caller will 
    // own the GIL. 
    PyEval_InitThreads(); 

    namespace python = boost::python; 
    python::def("f", +[] { 
    // For exposition, assert caller owns GIL before and after 
    // invoking function `f()`. 
    assert(gil_guard::owns_gil()); 
    f(); 
    assert(gil_guard::owns_gil()); 
    }); 
} 

互動用法:

>>> import threading 
>>> import example 
>>> t1 = threading.Thread(target=example.f) 
>>> t1.start(); print "Still running" 
in f() 
Still running 
calculating without gil... 
calling a python function 
calculating without gil... 
>>> t1.join() 
+0

你確定'的std :: shared_ptr的'?如果我沒有弄錯,那麼正確的命令將不會被調用。不會'boost :: variant '是一個更易於理解的解決方案嗎? – kay

+1

@Kay'std :: shared_ptr '會調用適當的析構函數。構造函數也是一個模板,採取的形式of'std :: shared_ptr的 :: shared_ptr的(Y * P)'。該標準要求'p'可轉換爲'T *',並且所述表達'刪除p'很好地形成。我同意''boost :: variant'可能比較容易理解。但是,執行類型擦除可以阻止任何人操縱元素。 –