pybind11을 사용하여 다중 스레드 C ++ 프로그램에 Python 인터프리터 포함

Dec 18 2020

타사 C ++ 라이브러리에서 Python 메서드를 호출 하도록 pybind11 을 사용하려고합니다 . 라이브러리는 다중 스레드이며 각 스레드는 Python 객체를 생성 한 다음 객체의 메서드를 여러 번 호출합니다.

내 문제는 py::gil_scoped_acquire acquire;교착 상태에 대한 호출 입니다. 문제를 재현하는 최소한의 코드는 다음과 같습니다. 내가 도대체 ​​뭘 잘못하고있는 겁니까?

// main.cpp
class Wrapper
{
public:
  Wrapper()
  {
    py::gil_scoped_acquire acquire;
    auto obj = py::module::import("main").attr("PythonClass")();
    _get_x = obj.attr("get_x");
    _set_x = obj.attr("set_x");
  }
  
  int get_x() 
  {
    py::gil_scoped_acquire acquire;
    return _get_x().cast<int>();
  }

  void set_x(int x)
  {
    py::gil_scoped_acquire acquire;
    _set_x(x);
  }

private:
  py::object _get_x;
  py::object _set_x;
};


void thread_func()
{
  Wrapper w;

  for (int i = 0; i < 10; i++)
  {
    w.set_x(i);
    std::cout << "thread: " << std::this_thread::get_id() << " w.get_x(): " << w.get_x() << std::endl;
    std::this_thread::sleep_for(100ms);    
  }
}

int main() {
  py::scoped_interpreter python;
  
  std::vector<std::thread> threads;

  for (int i = 0; i < 5; ++i)
    threads.push_back(std::thread(thread_func));

  for (auto& t : threads)
    t.join();

  return 0;
}

그리고 파이썬 코드 :

// main.py
class PythonClass:
    def __init__(self):
        self._x = 0

    def get_x(self):
        return self._x

    def set_x(self, x):
        self._x = x

관련 질문은 여기 와 여기 에서 찾을 수 있지만 문제를 해결하는 데 도움이되지 않았습니다.

답변

3 bavaza Dec 20 2020 at 18:37

작업자 스레드를 시작하기 전에 주 스레드에서 GIL을 해제하여 문제를 해결했습니다 (추가됨 py::gil_scoped_release release;). 관심있는 사람이라면 이제 다음이 작동합니다 (Python 객체 정리도 추가됨).

#include <pybind11/embed.h>  
#include <iostream>
#include <thread>
#include <chrono>
#include <sstream>

namespace py = pybind11;
using namespace std::chrono_literals;

class Wrapper
{
public:
  Wrapper()
  {
    py::gil_scoped_acquire acquire;
    _obj = py::module::import("main").attr("PythonClass")();
    _get_x = _obj.attr("get_x");
    _set_x = _obj.attr("set_x");

  }
  
  ~Wrapper()
  {
    _get_x.release();
    _set_x.release();
  }

  int get_x() 
  {
    py::gil_scoped_acquire acquire;
    return _get_x().cast<int>();
  }

  void set_x(int x)
  {
    py::gil_scoped_acquire acquire;
    _set_x(x);
  }

private:
  py::object _obj;
  py::object _get_x;
  py::object _set_x;
};


void thread_func(int iteration)
{
  Wrapper w;

  for (int i = 0; i < 10; i++)
  {
    w.set_x(i);
    std::stringstream msg;
    msg << "iteration: " << iteration << " thread: " << std::this_thread::get_id() << " w.get_x(): " << w.get_x() << std::endl;
    std::cout << msg.str();
    std::this_thread::sleep_for(100ms);    
  }
}

int main() {
  py::scoped_interpreter python;
  py::gil_scoped_release release; // add this to release the GIL

  std::vector<std::thread> threads;
  
  for (int i = 0; i < 5; ++i)
    threads.push_back(std::thread(thread_func, 1));

  for (auto& t : threads)
    t.join();

  return 0;
}
1 BasileStarynkevitch Dec 20 2020 at 15:11

Python 은 Global Interpreter Lock 이있는 것으로 알려져 있습니다 .

따라서 기본적으로 Python 인터프리터를 처음부터 작성하거나 Python 의 소스 코드를 다운로드하여 많이 개선해야합니다.

Linux를 사용하는 경우 여러 Python 인터프리터 (적절한 syscalls (2) 사용 , 프로세스 간 통신을 위해 pipe (7) 또는 unix (7) 사용 )-아마도 하나의 Python 프로세스가 각 C ++ 스레드와 통신하는 것을 고려할 수 있습니다.

내가 도대체 ​​뭘 잘못하고있는 겁니까?

파이썬으로 코딩하는 것은 그렇지 않으면 코딩되어야합니다. SBCL을 사용해 보셨습니까 ?

일부 라이브러리 (예 : Tensorflow )는 Python과 C ++ 모두에서 호출 할 수 있습니다. 아마도 그들로부터 영감을 얻을 수있을 것입니다 ...

실제로 강력한 Linux 시스템에 C ++ 스레드가 12 개만있는 경우 C ++ 스레드 당 하나의 Python 프로세스를 가질 수 있습니다. 따라서 각 C ++ 스레드에는 고유 한 Python 프로세스가 있습니다.

그렇지 않으면 GIL을 제거하기 위해 Python의 소스 코드를 개선하는 데 몇 년의 예산을 책정하십시오. Python의 C 코드를 분석하고 이해하는 데 도움이되도록 GCC 플러그인 을 코딩 할 수 있습니다 .