我阅读了一篇关于Habréс++
的文章,关于在使用子解释器的多线程应用程序中使用 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;
}
这是一个在代码中抽搐的python脚本:
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 行。此行将 Python 子解释器线程暂停一秒钟。如果取消注释,程序将挂起并且永远不会完成。同时,无法检测到程序停止的地方(恕我直言,这是在其中一个子解释器的构造函数中获取 GIL),使用调试器,因为当我逐步执行程序时,它正常结束.
基本上我有两个问题:
1)文章中所说的是否正确?
2) 确保在代码中并行执行 Python 脚本(无锁)如何正确(如果可能)c++
?
但没办法。你不能绕过 Python 中的 GIL。如果您在程序中需要多个解释器,请使用该项目:
https://sourceforge.net/projects/obasic/
这个解释器不是真正的 Python,而是 Basic。该解释器被设计为一个单独的 C++ 类,并在其类实例中包含其所有表。因此,在用户程序中,您可以拥有多个(理论上,只要您喜欢)解释器类的实例,它们不以任何方式相互依赖。因此,没有 GIL,您可以在解释器的不同实例中运行不同的脚本。当然,如果脚本引用用户程序的相同结构,那么这里可能需要同步。但这是一个完全不同的故事。