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(); ... }