2016-12-14 120 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'可能比较容易理解。但是,执行类型擦除可以阻止任何人操纵元素。 –