Прочитал статью на хабре об использовании 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++
?
Как всё-таки правильно (если это возможно) обеспечить параллельное выполнение пайтоновских скриптов (без блокировок) в коде c++?
А никак. В Питоне не обойти GIL. Если нужны несколько интерпретаторов в программе, то используйте проект:
https://sourceforge.net/projects/obasic/
Это интерпретатор правда не Питона, а Basic. Оформлен этот интерпретатор в виде отдельного С++ класса и все свои таблицы содержит в своем экземпляре класса. Соответственно, в программе пользователя можно иметь несколько (теоретически сколь угодно много) экземпляров класса интерпретатора, которые никак не зависят друг от друга. Соответственно, нет никаких GIL, можно запускать разные скрипты в разных экземплярах интерпретаторов. Конечно, если скрипты обращаются к одним и тем же структурам программы пользователя, то тут может понадобится синхронизация. Но это уже совсем другая история.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Необходимо блок поместить в рамкуНо при border: solid 1px grey; отображается только верхняя часть рамки(не хватает нижней рамки, правой и левой)
Похоже, аутлук добавляет таблицам отступы слева и справа, так как есть таблицы 290рх + 20рх + 290рх и они не влазят в общую таблицу шириной 600рхМожет...
Подскажите как добавить новую запись в связанные таблицыДелаю так `