Nhúng trình thông dịch Python trong chương trình C ++ đa luồng với pybind11

Dec 18 2020

Tôi đang cố gắng sử dụng pybind11 để tạo thư viện C ++ của bên thứ 3 gọi một phương thức Python. Thư viện là đa luồng và mỗi luồng tạo ra một đối tượng Python, sau đó thực hiện nhiều lệnh gọi đến các phương thức của đối tượng.

Vấn đề của tôi là cuộc gọi đến py::gil_scoped_acquire acquire;bế tắc. Một đoạn mã tối thiểu tái tạo sự cố được đưa ra bên dưới. Tôi đang làm gì sai?

// 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;
}

và mã Python:

// 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

Các câu hỏi liên quan có thể được tìm thấy ở đây và ở đây , nhưng không giúp tôi giải quyết vấn đề.

Trả lời

3 bavaza Dec 20 2020 at 18:37

Tôi đã quản lý để giải quyết vấn đề bằng cách giải phóng GIL trong luồng chính, trước khi bắt đầu các luồng công nhân (đã thêm py::gil_scoped_release release;). Đối với bất kỳ ai quan tâm, phần sau hiện hoạt động (cũng được thêm vào việc dọn dẹp các đối tượng 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 được biết là có Khóa thông dịch viên toàn cầu .

Vì vậy, về cơ bản bạn cần phải viết trình thông dịch Python của riêng mình từ đầu, hoặc tải xuống mã nguồn của Python và cải thiện nó rất nhiều.

Nếu bạn đang sử dụng Linux, bạn có thể cân nhắc chạy nhiều trình thông dịch Python (sử dụng syscalls thích hợp (2) , với pipe (7) hoặc unix (7) để giao tiếp giữa các quy trình) - có lẽ một quy trình Python giao tiếp với từng luồng C ++ của bạn.

Tôi đang làm gì sai?

Mã hóa bằng Python một cái gì đó nên được mã hóa theo cách khác. Bạn đã xem xét thử SBCL ?

Một số thư viện (ví dụ: Tensorflow ) có thể được gọi từ cả Python và C ++. Có lẽ bạn có thể lấy cảm hứng từ họ ...

Trong thực tế, nếu bạn chỉ có một tá chuỗi C ++ trên một máy Linux mạnh mẽ, bạn có thể đủ khả năng có một quy trình Python cho mỗi chuỗi C ++. Vì vậy, mỗi luồng C ++ sẽ có quy trình Python đồng hành của riêng nó.

Nếu không, hãy dành vài năm làm việc để cải thiện mã nguồn của Python để loại bỏ GIL của nó. Bạn có thể viết mã plugin GCC của mình để giúp bạn thực hiện nhiệm vụ đó - phân tích và hiểu mã C của Python.