Python嵌入式冻结应用中如何成功启动子进程?
Windows嵌入式冻结Python应用中multiprocessing子进程启动失败
我了解到Python的multiprocessing模块中子进程的入口是multiprocessing.spawn.spawn_main,根据multiprocessing.spawn.get_command_line的实现:
def get_command_line(**kwds): ''' Returns prefix of command line used for spawning a child process ''' if getattr(sys, 'frozen', False): return ([sys.executable, '--multiprocessing-fork'] + ['%s=%r' % item for item in kwds.items()]) else: prog = 'from multiprocessing.spawn import spawn_main; spawn_main(%s)' prog %= ', '.join('%s=%r' % item for item in kwds.items()) opts = util._args_from_interpreter_flags() exe = get_executable() return [exe] + opts + ['-c', prog, '--multiprocessing-fork']
在常规Python环境中,Windows上启动子进程的命令如下:
python.exe -c "from multiprocessing.spawn import spawn_main; spawn_main(parent_pid=6872, pipe_handle=520)" --multiprocessing-fork
而在Python嵌入式冻结应用中,Windows上的子进程启动命令为:
my_program.exe --multiprocessing-fork parent_pid=8752 pipe_handle=1552
因此我认为需要在检测到--multiprocessing-fork命令行参数时,手动调用multiprocessing.spawn.spawn_main。以下是我的C++实现代码:
#include <Python.h> #include <cstdlib> #include <string> #include <filesystem> #include <string> #include <vector> #include <sstream> static int multiprocess_main(int argc, char** argv) { if (argc < 4) { fprintf(stderr, "Too little arguments to know how to run a python multiprocess.\n"); return 1; } PyStatus status; PyConfig config; PyConfig_InitPythonConfig(&config); config.module_search_paths_set = 1; // sys.path will be: [''] std::filesystem::path executable_path(argv[0]); auto executable_directory = executable_path.parent_path().wstring(); config.write_bytecode = 0; // In Python3.11, this will not work. status = PyConfig_SetString(&config, &config.stdlib_dir, executable_directory.c_str()); if (PyStatus_Exception(status)) { PyConfig_Clear(&config); fprintf(stderr, "%s", PyStatus_IsError(status) != 0 ? status.err_msg : "Failed to set sys._stdlib_dir."); return 1; } status = Py_InitializeFromConfig(&config); if (PyStatus_Exception(status)) { PyConfig_Clear(&config); fprintf(stderr, "%s", PyStatus_IsError(status) != 0 ? status.err_msg : "Failed to initialize CPython from config."); return 1; } PyConfig_Clear(&config); int exitcode = 0; // Set sys._stdlib_dir manually PyObject* stdlib_dir_value; const auto* stdlibdir = executable_directory.c_str(); stdlib_dir_value = PyUnicode_FromWideChar(stdlibdir, wcslen(stdlibdir)); if (stdlib_dir_value == nullptr) { fprintf(stderr, "Could not convert directory name to unicode\n"); PyErr_Print(); return 1; } if (PySys_SetObject("_stdlib_dir", stdlib_dir_value) == -1) { fprintf(stderr, "Could not set sys._stdlib_dir\n"); Py_DECREF(stdlib_dir_value); PyErr_Print(); return 1; } Py_DECREF(stdlib_dir_value); // Set sys.frozen if (PySys_SetObject("frozen", Py_True) == -1) { fprintf(stderr, "Could not set sys.frozen\n"); PyErr_Print(); return 1; } // Set "--multiprocessing-fork" to sys.argv to make multiprocessing.spawn.is_forking work PyRun_SimpleString("import sys; sys.argv.append('--multiprocessing-fork')"); // from multiprocessing.spawn import spawn_main; spawn_main(parent_pid=xxx, pipe_handle=yyy) // Currently only win32 is supported PyObject *spawn, *spawn_main, *spawnargs, *parent_pid, *pipe_handle, *result; for (int arg_idx = 2; arg_idx < argc; arg_idx++) { std::stringstream argss(argv[arg_idx]); std::string segment; std::vector<std::string> seglist; while (std::getline(argss, segment, '=')) { seglist.push_back(segment); } if (seglist[0] == "parent_pid") { fprintf(stderr, "parent_pid=%s\n", seglist[1].c_str()); parent_pid = PyLong_FromString(seglist[1].c_str(), nullptr, 0); if (parent_pid == nullptr) { fprintf(stderr, "Failed to parse parent_pid=%s while starting multiprocess\n", seglist[2].c_str()); if (pipe_handle != nullptr) { Py_DECREF(pipe_handle); } PyErr_Print(); return 1; } } if (seglist[0] == "pipe_handle") { fprintf(stderr, "pipe_handle=%s\n", seglist[1].c_str()); pipe_handle = PyLong_FromString(seglist[1].c_str(), nullptr, 0); if (pipe_handle == nullptr) { fprintf(stderr, "Failed to parse pipe_handle=%s while starting multiprocess\n", seglist[2].c_str()); if (parent_pid != nullptr) { Py_DECREF(parent_pid); } PyErr_Print(); return 1; } } } if (pipe_handle == nullptr) { fprintf(stderr, "Could not parse pipe_handle while starting multiprocess\n"); return 1; } spawn = PyImport_ImportModule("multiprocessing.spawn"); if (spawn == nullptr) { fprintf(stderr, "Could not import multiprocessing.spawn\n"); Py_DECREF(pipe_handle); if (parent_pid != nullptr) { Py_DECREF(parent_pid); } PyErr_Print(); return 1; } spawn_main = PyObject_GetAttrString(spawn, "spawn_main"); if (spawn_main == nullptr) { fprintf(stderr, "Could not access function: multiprocessing.spawn.spawn_main\n"); Py_DECREF(spawn); Py_DECREF(pipe_handle); if (parent_pid != nullptr) { Py_DECREF(parent_pid); } PyErr_Print(); return 1; } if (parent_pid != nullptr) { spawnargs = PyTuple_Pack(2, pipe_handle, parent_pid); } else { spawnargs = PyTuple_Pack(1, pipe_handle); } if (spawnargs == nullptr) { fprintf(stderr, "Could not create arguments for multiprocessing.spawn.spawn_main\n"); Py_DECREF(spawn); Py_DECREF(spawn_main); Py_DECREF(pipe_handle); if (parent_pid != nullptr) { Py_DECREF(parent_pid); } PyErr_Print(); return 1; } result = PyObject_Call(spawn_main, spawnargs, nullptr); Py_DECREF(spawn); Py_DECREF(spawn_main); Py_DECREF(spawnargs); Py_DECREF(pipe_handle); if (parent_pid != nullptr) { Py_DECREF(parent_pid); } if (result == nullptr) { PyErr_Print(); return 1; } Py_DECREF(result); return exitcode; } int main(int argc, char** argv) { if (argc >= 2 && strcmp(argv[1], "--multiprocessing-fork") == 0) { return multiprocess_main(argc, argv); } // Other normal stuffs }
测试用的Python脚本如下:
import multiprocessing from multiprocessing import spawn def f(): print("hello world!") if __name__ == "__main__": multiprocessing.freeze_support() multiprocessing.Process(target=f).start()
但运行程序时,首次会输出如下错误:
Traceback (most recent call last): File "<frozen multiprocessing.spawn>", line 113, in spawn_main File "<frozen multiprocessing.reduction>", line 79, in duplicate PermissionError: [WinError 5] Access is denied
每次重新编译生成可执行文件后首次运行都会出现该错误,之后无论运行多少次,都会出现如下错误:
Traceback (most recent call last): File "<frozen multiprocessing.spawn>", line 108, in spawn_main OSError: [WinError 87] The parameter is incorrect
我不清楚问题出在哪里,希望能得到帮助,目标是在Windows系统中成功启动子进程。
内容的提问来源于stack exchange,提问作者Tac Uchiha
相关产品推荐
相关产品推荐

