boost.python не поддерживает параллелизм? - PullRequest
9 голосов
/ 04 ноября 2011

Я пытаюсь обернуть часть кода C ++ в библиотеку Python с помощью boost.python, однако я обнаружил, что несколько экземпляров не могут работать одновременно:

код (C ++):

class Foo{
public:
    Foo(){}
    void run(){
        int seconds = 2;
        clock_t endwait;
        endwait = clock () + seconds * CLOCKS_PER_SEC ;
        while (clock() < endwait) {}
    }   

};

BOOST_PYTHON_MODULE(run_test)
{
   using namespace boost::python;

   class_<Foo>("test", init<>())
      .def("run", &Foo::run)
      ;   

}

, который компилируется с использованием CMake (CMake):

add_library(run_test SHARED run_test.cpp)
target_link_libraries(run_test boost_python python2.7)

и тестируется с использованием следующего кода (Python):

class Dos(threading.Thread):
    def run(self):
        printl('performing DoS attack')

        proc = test()
        proc.run()

for i in range(5):
    t = Dos()
    t.start()

Выходные данные указывают, что кодраспараллелены очень странным образом.Каждый поток должен занимать всего 2 секунды, и на моем четырехъядерном компьютере должны одновременно выполняться 4 потока:

[2011-11-04 13:57:01] performing DoS attack
 [2011-11-04 13:57:01] performing DoS attack
[2011-11-04 13:57:05] performing DoS attack
 [2011-11-04 13:57:05] performing DoS attack
[2011-11-04 13:57:09] performing DoS attack

спасибо за помощь!

1 Ответ

17 голосов
/ 04 ноября 2011

То, с чем вы сталкиваетесь, это глобальная блокировка интерпретатора python.GIL позволяет запускать только один поток в интерпретаторе python.

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

Это можно легко исправить с помощью небольшого обратного RAII:

class releaseGIL{
public:
    inline releaseGIL(){
        save_state = PyEval_SaveThread();
    }

    inline ~releaseGIL(){
        PyEval_RestoreThread(save_state);
    }
private:
    PyThreadState *save_state;
};

Теперь вы можете изменить свой код следующим образом:

class Foo{
public:
    Foo(){}
    void run(){
        {
            releaseGIL unlock = releaseGIL();
            int seconds = 2;
            clock_t endwait;
            endwait = clock () + seconds * CLOCKS_PER_SEC ;
            while (clock() < endwait) {}
        }
    }   
};

Это ОЧЕНЬ важнообратите внимание, что вы НЕ ДОЛЖНЫ касаться кода Python, данных Python или вызывать интерпретатор, не удерживая GIL.Это приведет к сбою вашего переводчика.

Также возможно пойти другим путем.Поток, в настоящее время не содержащий GIL, может получить его и сделать вызовы в python.Это может быть поток, выпустивший GIL ранее, или поток, начатый в c ++ и никогда не имевший GIL.Вот класс RAII для этого:

class AcquireGIL 
{
public:
    inline AcquireGIL(){
        state = PyGILState_Ensure();
    }

    inline ~AcquireGIL(){
        PyGILState_Release(state);
    }
private:
    PyGILState_STATE state;
};

Использование оставлено в качестве упражнения для студента.

Дополнительные примечания (я всегда забываю об этом упоминать):

Если вы собираетесь возиться с GIL в c ++, определение вашего модуля должно начинаться с этого кода:

BOOST_PYTHON_MODULE(ModuleName)
{
    PyEval_InitThreads();

    ...
}
...