【问题标题】:boost.python c++ multithreadingboost.python c++ 多线程
【发布时间】:2017-04-29 05:11:53
【问题描述】:

我正在编写一个包含 c++ 模块的 python 程序(.so,使用 boost.python)。
我正在启动几个运行 c++ 函数的 python 线程。

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 的最佳方法是什么?

谢谢! 加尔

【问题讨论】:

  • 我不知道 boost::python (但感谢您提及名称,它看起来很有趣),但这个答案看起来可能会解决您的问题:struct no_gil
  • 谢谢。我看到了那些 saveThread/restoreThread 方法,但我仍然需要在我的函数中重新获取 Gil 才能调用一些 Python 代码。

标签: python c++ multithreading boost-python gil


【解决方案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()

【讨论】:

  • 你确定std::shared_ptr&lt;void&gt;?如果我没记错的话,就不会调用正确的 d'tor。 boost::variant&lt;with_gil, without_gil&gt; 不是更容易理解的解决方案吗?
  • @Kay std::shared_ptr&lt;void&gt; 将调用适当的析构函数。构造函数也是一个模板,形式为std::shared_ptr&lt;T&gt;::shared_ptr&lt;Y&gt;(Y* p)。该标准要求p 可转换为T*,并且表达式delete p 格式正确。我同意boost::variant 一目了然可能更容易理解。但是,执行类型擦除可以阻止任何人操纵元素。
  • 详细代码+解释。谢谢。应在常见问题解答中列出作为良好 SO 答案的示例。然而,这条评论不是。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-05-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多