Menyematkan interpreter Python dalam program C ++ multi-utas dengan pybind11

Dec 18 2020

Saya mencoba menggunakan pybind11 untuk membuat pustaka C ++ pihak ketiga memanggil metode Python. Perpustakaannya multithread, dan setiap utas membuat objek Python, dan kemudian melakukan banyak panggilan ke metode objek.

Masalah saya adalah bahwa panggilan ke py::gil_scoped_acquire acquire;jalan buntu. Kode minimal yang mereproduksi masalah diberikan di bawah ini. Apa yang saya lakukan salah?

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

dan kode 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

Pertanyaan terkait dapat ditemukan di sini dan di sini , tetapi tidak membantu saya memecahkan masalah.

Jawaban

3 bavaza Dec 20 2020 at 18:37

Saya berhasil menyelesaikan masalah dengan melepaskan GIL di utas utama, sebelum memulai utas pekerja (ditambahkan py::gil_scoped_release release;). Bagi siapa pun yang tertarik, berikut ini sekarang berfungsi (juga menambahkan pembersihan objek 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 dikenal memiliki Kunci Interpreter Global .

Jadi pada dasarnya Anda perlu menulis penerjemah Python Anda sendiri dari awal, atau mengunduh kode sumber Python dan memperbaikinya banyak.

Jika Anda menggunakan Linux, Anda dapat mempertimbangkan untuk menjalankan banyak interpreter Python (menggunakan syscall yang sesuai (2) , dengan pipa (7) atau unix (7) untuk komunikasi antarproses ) - mungkin satu proses Python yang berkomunikasi dengan setiap thread C ++ Anda.

Apa yang saya lakukan salah?

Pengkodean dengan Python sesuatu yang harus dikodekan sebaliknya. Apakah Anda mempertimbangkan untuk mencoba SBCL ?

Beberapa pustaka (misalnya Tensorflow ) dapat dipanggil dari Python dan C ++. Mungkin Anda bisa mengambil inspirasi dari mereka ...

Dalam praktiknya, jika Anda hanya memiliki selusin utas C ++ pada mesin Linux yang kuat, Anda dapat membeli satu proses Python per utas C ++. Jadi setiap utas C ++ akan memiliki proses Python pendampingnya sendiri.

Jika tidak, anggarkan beberapa tahun kerja untuk meningkatkan kode sumber Python untuk menghapus GIL-nya. Anda dapat mengkodekan plugin GCC Anda untuk membantu Anda dalam tugas itu -menganalisis dan memahami kode C Python.