boost.python non supporta il parallelismo?

Sto cercando di avvolgere un pezzo di codice C ++ in python lib usando boost.python, tuttavia, ho scoperto che più istanze non possono essere eseguite contemporaneamente:

codice (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_("test", init()) .def("run", &Foo::run) ; } 

che è compilato usando CMake (CMake):

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

e testato con il seguente codice (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() 

L’output indica che il codice è parallelizzato in un modo molto strano. Ogni thread dovrebbe richiedere solo 2 secondi e 4 thread dovrebbero essere eseguiti simultaneamente sulla mia macchina quadcore:

     [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 

    grazie per l’aiuto!

    Quello che stai incontrando è il Python Global Interpreter Lock. GIL consente solo l’esecuzione di un thread alla volta nell’interprete python.

    Uno dei vantaggi di Boost.Python è che puoi rilasciare GIL, fare cose in C ++ e poi riprenderlo quando hai finito. Questa è anche una responsabilità comunque. Python rilascia normalmente GIL a intervalli regolari, per dare agli altri thread la possibilità di correre. Se sei in C ++, questo è il tuo lavoro. Se si fanno i numeri di crunch per 2 ore mentre si tiene il GIL, si congelerà l’intero interprete.

    Questo può essere facilmente risolto con un piccolo RAII inverso:

     class releaseGIL{ public: inline releaseGIL(){ save_state = PyEval_SaveThread(); } inline ~releaseGIL(){ PyEval_RestoreThread(save_state); } private: PyThreadState *save_state; }; 

    Ora puoi cambiare il tuo codice in questo modo:

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

    È MOLTO importante notare che NON DEVI toccare alcun codice python, o dati python o chiamare l'interprete senza tenere GIL. Ciò causerà l'arresto anomalo dell'interprete.

    È anche ansible andare dall'altra parte. Un thread che al momento non contiene GIL può acquisirlo e fare chiamate a python. Questo può essere un thread che ha rilasciato GIL in precedenza, o uno che è iniziato in c ++ e non ha mai avuto il GIL. Ecco la class RAII per questo:

     class AcquireGIL { public: inline AcquireGIL(){ state = PyGILState_Ensure(); } inline ~AcquireGIL(){ PyGILState_Release(state); } private: PyGILState_STATE state; }; 

    L'uso è lasciato come esercizio per lo studente.

    Nota aggiuntiva (mi dimentico sempre di menzionarlo):

    Se stai per scherzare con GIL in c ++, la tua definizione del modulo deve iniziare con questo codice:

     BOOST_PYTHON_MODULE(ModuleName) { PyEval_InitThreads(); ... }