Pybind11 многопроцессорный зависает - PullRequest
2 голосов
/ 17 января 2020

Я пишу приложение, использующее Pybind11 для встраивания интерпретатора Python (Windows, 64 бит, Visual C ++ 2017). С Python мне нужно порождать несколько процессов, но, похоже, это не работает. В качестве теста я пробую следующий код:

import multiprocessing
import os
import sys
import time
print("This is the name of the script: ", sys.argv[0])
print("Number of arguments: ", len(sys.argv))
print("The arguments are: " , str(sys.argv))
prefix=str(os.getpid())+"-"
if len(sys.argv) > 1:
    __name__ = "__mp_main__"

def print_cube(num):
    """
    function to print cube of given num
    """
    print("Cube: {}".format(num * num * num))

def print_square(num):
    """
    function to print square of given num
    """
    print("Square: {}".format(num * num))

print(__name__)

if __name__ == "__main__":
    print(prefix, "checkpoint 1")
    # creating processes
    p1 = multiprocessing.Process(target=print_square, args=(10, ))
    p1.daemon = True
    p2 = multiprocessing.Process(target=print_cube, args=(10, ))

    # starting process 1
    p1.start()
    print(prefix, "checkpoint 2")

    # starting process 2
    p2.start()
    print(prefix, "checkpoint 3")

    # wait until process 1 is finished
    print(prefix, "checkpoint 4")
    p1.join()
    print(prefix, "checkpoint 5")
    # wait until process 2 is finished
    p2.join()
    print(prefix, "checkpoint 6")

    # both processes finished
    print("Done!")
print(prefix, "checkpoint 7")
time.sleep(10)

Запустив его с помощью Python из командной строки, я получаю:

This is the name of the script:  mp.py
Number of arguments:  1
The arguments are:  ['mp.py']
__main__
12872- checkpoint 1
12872- checkpoint 2
This is the name of the script:  C:\tmp\mp.py
Number of arguments:  1
The arguments are:  ['C:\\tmp\\mp.py']
__mp_main__
7744- checkpoint 7
Square: 100
12872- checkpoint 3
12872- checkpoint 4
12872- checkpoint 5
This is the name of the script:  C:\tmp\mp.py
Number of arguments:  1
The arguments are:  ['C:\\tmp\\mp.py']
__mp_main__
15020- checkpoint 7
Cube: 1000
12872- checkpoint 6
Done!
12872- checkpoint 7

, что правильно. Если я попробую то же самое из проекта C ++ с Pybind11, то получится:

This is the name of the script:  C:\AGPX\Documenti\TestPyBind\x64\Debug\TestPyBind.exe
Number of arguments:  1
The arguments are:  ['C:\\AGPX\\Documenti\\TestPyBind\\x64\\Debug\\TestPyBind.exe']
__main__
4440- checkpoint 1
This is the name of the script:  C:\AGPX\Documenti\TestPyBind\x64\Debug\TestPyBind.exe
Number of arguments:  4
The arguments are:  ['C:\\AGPX\\Documenti\\TestPyBind\\x64\\Debug\\TestPyBind.exe', '-c', 'from multiprocessing.spawn import spawn_main; spawn_main(parent_pid=4440, pipe_handle=128)', '--multiprocessing-fork']
__mp_main__
10176- checkpoint 7

Обратите внимание, что в этом случае переменная __name__ всегда устанавливается на '__main__', поэтому я имею изменить его вручную (для порожденных процессов) на '__mp_main__' (я могу обнаружить дочерние процессы благодаря sys.argv). Это первое странное поведение. У родительского процесса pid 4440, и я вижу процесс в проводнике процессов. Первый дочерний процесс имеет pid 10176, и он достигает конечной «контрольной точки 7», и процесс исчезает из проводника процессов. Тем не менее, основной процесс не печатает «контрольную точку 2», то есть похоже, что он зависает в «p1.start ()», и я не могу понять, почему. Полный код C ++:

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>
#include <pybind11/embed.h>
#include <iostream>

namespace py = pybind11;
using namespace py::literals;

int wmain(int argc, wchar_t **argv)
{
    py::initialize_interpreter();
    PySys_SetArgv(argc, argv);

    std::string pyCode = std::string(R"(
import multiprocessing
import os
import sys
import time
print("This is the name of the script: ", sys.argv[0])
print("Number of arguments: ", len(sys.argv))
print("The arguments are: " , str(sys.argv))
prefix=str(os.getpid())+"-"
if len(sys.argv) > 1:
    __name__ = "__mp_main__"

def print_cube(num):
    """
    function to print cube of given num
    """
    print("Cube: {}".format(num * num * num))

def print_square(num):
    """
    function to print square of given num
    """
    print("Square: {}".format(num * num))

print(__name__)

if __name__ == "__main__":
    print(prefix, "checkpoint 1")
    # creating processes
    p1 = multiprocessing.Process(target=print_square, args=(10, ))
    p1.daemon = True
    p2 = multiprocessing.Process(target=print_cube, args=(10, ))

    # starting process 1
    p1.start()
    print(prefix, "checkpoint 2")

    # starting process 2
    p2.start()
    print(prefix, "checkpoint 3")

    # wait until process 1 is finished
    print(prefix, "checkpoint 4")
    p1.join()
    print(prefix, "checkpoint 5")
    # wait until process 2 is finished
    p2.join()
    print(prefix, "checkpoint 6")

    # both processes finished
    print("Done!")
print(prefix, "checkpoint 7")
time.sleep(10)
)");
    try
    {
        py::exec(pyCode);
    } catch (const std::exception &e) {
        std::cout << e.what();
    }
    py::finalize_interpreter();
}

Может кто-нибудь объяснить мне, как решить эту проблему, пожалуйста?

Заранее спасибо (и я прошу прощения за мой английский sh).

1 Ответ

2 голосов
/ 18 января 2020

Хорошо, благодаря этой ссылке: https://blender.stackexchange.com/questions/8530/how-to-get-python-multiprocessing-module-working-on-windows, я решил эту странную проблему (которая, похоже, Windows связана). Это не проблема Pybind11, а сам API Python C. Вы можете решить эту проблему, задав sys.executable равно пути к исполняемому файлу интерпретатора python (python .exe) и записав код python в файл и указав путь к переменной __file__. То есть я должен добавить:

import sys
sys.executable = "C:\\Users\\MyUserName\\Miniconda3\\python.exe"
__file__ = "C:\\tmp\\run.py"

, и мне нужно записать код python в файл, заданный __file__, то есть:

FILE *f = nullptr;
fopen_s(&f, "c:\\tmp\\run.py", "wt");
fprintf(f, "%s", pyCode.c_str());
fclose(f);

непосредственно перед выполнить py::exec(pyCode).

Кроме того, код:

if len(sys.argv) > 1:
    __name__ = "__mp_main__"

больше не нужен. Однако обратите внимание, что таким образом запущенные процессы больше не внедряются, и, к сожалению, если вы хотите напрямую передать им модуль C ++, вы не сможете этого сделать.

Надеюсь, это может помочь кому-то еще.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...