мультипоточность boost.python c ++

Я пишу программу на Python, которая включает в себя модуль C ++ (.so, с помощью boost.python).
Я запускаю несколько потоков 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);
}

И код питона:

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

Я столкнулся с проблемой: «Все еще работает!» сообщение не отображается, и я обнаружил, что поток c ++ содержит GIL.

Каков наилучший метод обработки GIL в моем случае запуска кода C ++ из кода Python?

Спасибо!
гал

3

Решение

Я часто нахожу, что используя RAII стиле классы для управления Глобальная блокировка интерпретатора (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)

Вот полный пример демонстрирующий Управление 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()
2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]