Вызов пайтоновских скриптов из кода с++

130
27 июня 2021, 20:50

Прочитал статью на хабре об использовании Python в многопоточном приложении с++ с использованием субинтерпретаторов. При этом, как заявляется в статье, это позволяет обеспечить "чистую многопоточность", тобишь субинтрепритаторы не лочатся глобальным GIL (прошу прощения за тавтологию), а работают параллельно. В общем, решил попробовать и написал вот такой код:

// main.cpp
#include <boost/python.hpp>
#include <cstdlib>
#include <iostream>
#include <list>
#include <mutex>
#include <thread>
#include <unistd.h>
#include <unordered_map>
class PyMainThread final {
public:
  PyMainThread()
      : mainThreadState_{nullptr} {
    ::Py_Initialize();
    ::PyEval_InitThreads();
    mainGIL_         = ::PyGILState_Ensure();
    mainThreadState_ = ::PyEval_SaveThread();
  }
  ~PyMainThread() {
    ::PyEval_RestoreThread(mainThreadState_);
    ::PyGILState_Release(mainGIL_);
  }
  PyMainThread(const PyMainThread &) = delete;
  PyMainThread &operator=(const PyMainThread &) = delete;
private:
  ::PyGILState_STATE mainGIL_;
  ::PyThreadState *  mainThreadState_;
};
static std::mutex mutex;
class PySubThread final {
public:
  PySubThread()
      : mainThreadState_{nullptr}
      , subThreadState_{nullptr}
      , newInterpreterThread_{nullptr} {
    std::lock_guard<std::mutex> lock(mutex);
    mainGIL_         = ::PyGILState_Ensure();
    mainThreadState_ = ::PyThreadState_Get();
    newInterpreterThread_ = ::Py_NewInterpreter();
    ::PyThreadState_Swap(newInterpreterThread_);
    subThreadState_ = ::PyEval_SaveThread();
    subGIL_         = ::PyGILState_Ensure();
  }
  ~PySubThread() {
    std::lock_guard<std::mutex> lock(mutex);
    ::PyGILState_Release(subGIL_);
    ::PyEval_RestoreThread(subThreadState_);
    ::Py_EndInterpreter(newInterpreterThread_);
    ::PyThreadState_Swap(mainThreadState_);
    ::PyGILState_Release(mainGIL_);
  }
  PySubThread(const PySubThread &) = delete;
  PySubThread &operator=(const PySubThread &) = delete;
private:
  ::PyGILState_STATE mainGIL_;
  ::PyGILState_STATE subGIL_;
  ::PyThreadState *mainThreadState_;
  ::PyThreadState *subThreadState_;
  ::PyThreadState *newInterpreterThread_;
};
std::unordered_map<std::thread::id, std::unique_ptr<PySubThread>> pyThreads;
std::mutex                                                        pyM;
void foo(const std::string &pythonFile, int iterationCount) {
  volatile PySubThread subThread;
  for (int i = 0; i < iterationCount; ++i) {
    try {
      // std::this_thread::sleep_for(std::chrono::milliseconds{1000});
      boost::python::object import = boost::python::import("__main__");
      boost::python::object result =
          boost::python::exec_file(pythonFile.c_str(), import.attr("__dict__"));
    } catch (boost::python::error_already_set &) {
      ::PyErr_Print();
    }
  }
}
int main(int argc, char *argv[]) {
  if (argc < 4) {
    std::cerr << "you must set script, threads and count of iterations"
              << std::endl;
    return EXIT_FAILURE;
  }
  volatile PyMainThread mainThread;
  std::list<std::thread> threads;
  for (int i = 0; i < std::atoi(argv[2]); ++i) {
    threads.emplace_back(foo, argv[1], argv[3]);
  }
  for (auto &i : threads) {
    i.join();
  }
  return EXIT_SUCCESS;
}

а это питоновский скрипт, который дёргается в коде:

import time

def caculate(a, b):
  return a + b

def main():
  print("sleep 2 seconds ...")
  time.sleep(2)
  print("... and calculate")
  return caculate(-10, 20)

main()
# cmake
cmake_minimum_required(VERSION 3.5)
project(tmp)
find_package(Boost COMPONENTS python37 REQUIRED)
find_package(Python3 COMPONENTS Development REQUIRED)
find_package(Threads REQUIRED)
add_executable(${PROJECT_NAME} main.cpp)
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_11)
target_link_libraries(${PROJECT_NAME} PRIVATE
  Boost::boost
  ${Python3_LIBRARIES}
  ${Boost_LIBRARIES}
  Threads::Threads
  )
target_include_directories(${PROJECT_NAME} PRIVATE ${Python3_INCLUDE_DIRS})

При запуске в несколько потоков все работает, как и говорилось в статье, НО! Обратите внимание на 88ую строку в с++ коде. Эта строка ставит поток с пайтоновским субинтерпретатором на паузу в течение одной секунды. Если ее раскомментировать, то программа зависнет и никогда не завершится. При этом засечь место, где программа останавливается (ИМХО это получение GIL в конструкторе одного из subinterpretators), с помощью дебаггера не получается, так как, когда выполняю программу пошагово, то она завершается нормально.

В общем у меня два вопроса:

1) Верно ли то, что приводится в статье?

2) Как всё-таки правильно (если это возможно) обеспечить параллельное выполнение пайтоновских скриптов (без блокировок) в коде c++?

Answer 1

Как всё-таки правильно (если это возможно) обеспечить параллельное выполнение пайтоновских скриптов (без блокировок) в коде c++?

А никак. В Питоне не обойти GIL. Если нужны несколько интерпретаторов в программе, то используйте проект:

https://sourceforge.net/projects/obasic/

Это интерпретатор правда не Питона, а Basic. Оформлен этот интерпретатор в виде отдельного С++ класса и все свои таблицы содержит в своем экземпляре класса. Соответственно, в программе пользователя можно иметь несколько (теоретически сколь угодно много) экземпляров класса интерпретатора, которые никак не зависят друг от друга. Соответственно, нет никаких GIL, можно запускать разные скрипты в разных экземплярах интерпретаторов. Конечно, если скрипты обращаются к одним и тем же структурам программы пользователя, то тут может понадобится синхронизация. Но это уже совсем другая история.

READ ALSO
Некорректно отображается рамка вокруг div

Некорректно отображается рамка вокруг div

Необходимо блок поместить в рамкуНо при border: solid 1px grey; отображается только верхняя часть рамки(не хватает нижней рамки, правой и левой)

107
Верстка письма под ms outlook

Верстка письма под ms outlook

Похоже, аутлук добавляет таблицам отступы слева и справа, так как есть таблицы 290рх + 20рх + 290рх и они не влазят в общую таблицу шириной 600рхМожет...

114
Добавление новой записи в связанные таблицы

Добавление новой записи в связанные таблицы

Подскажите как добавить новую запись в связанные таблицыДелаю так `

117
Полный экран Cefsharp WPF

Полный экран Cefsharp WPF

Собственно есть условный код:

109