Afinant l’entorn multi thread i multiples interprets de python
September 27, 2007 – 6:57 pmSeguint el fil del post anterior, voldria fer algunes correccions i afegir més informació. Certament resulta suficientment complex el tema de crear un entorn segur de multiples interprets embebits de python en un codi en C com per seguir-ne parlant.
Del codi anterior, acabat a les tantes de la nit, en puc extreure algunes problemàtiques implicites i que forçosament m'han fet replantejar els usos de les funcions que ens serveix Python per tractar els contextes de threads i de retruc els interprets associats.
Python ens dona dos tipus de funcions, unes que podem anomenar de forma entenedora com a alt nivell, aquestes funcions són les seguents :
- PyEval_AdquireThread (PyThreadState * tstate): Aquesta primera funció ens permet associar el context del thread actual al context python que passaem com a primer parametre, i ens demana explicitament que el context anterior del thread no sigui NULL
- PyEval_ReleaseThread (PyThreadState * tstate ) : Aquesta segona funcio serveix per destruir el thread, el context python del thread passara a ser NULL.
- PyThreadState * tstate PyEval_SaveThread : Retorna el context python actual del thread
- PyEval_RestoreThread (PyThreadState * tstate ) : Molt semblant a la funció AdquireThread
Totes aquestes funcions de per si tanquen el GIL o el cedeixen en les diferents versions i depenent de si fem un Adquire/Restore o Release.
Per tant un us lògic d'aquestes funcions passaria a un codi semblant al seguent
PyEval_AdquireThread ( tstate ) // fer alguna cosa de python en un context de thread python identificat // per la variable tstate PyEval_ReleaseThread ( tstate )
Recordem també que un thread amb context de python esta associat a un interpret, i que un interpret pot tenir n threads que l'accedeixin.
Tal i com ja comentavem en l'article anterior l'us dels interprets ens permetre seperar de forma exhaustiva contextos d'aplicacions python en un sol programa C, diguis mod_python.
Del codi que us presentava en el post anterior, hem va sorgir el dubte de si mod_python era capaç d'executar multiples instancies d'un virtual host associat a un interpret, doncs be, mitjançant aquesta pregunta he apres que realment es aixins i que el codi presentat anteriorment era incorrecte.
El codi correcte de la funcio do_something_python_code corregit seria una cosa com aquesta :
void * do_something_python_code (void * id) { char command[512]; int id_cast = (int ) id; PyThreadState *tstate; printf("Thread %d execution n", id_cast); PyEval_AcquireLock(); interpreter[id_cast] = Py_NewInterpreter(); PyThreadState_Swap(NULL); PyEval_ReleaseLock( ); PyEval_AcquireThread(interpreter[id_cast]); // do something sprintf(command, "print \"(%d %cs)\" %c dir()n�", id_cast, '%', '%'); PyRun_SimpleString(command); PyRun_SimpleString("import sysn"); PyRun_SimpleString("import timen"); PyRun_SimpleString("time.sleep(3)n"); sprintf(command, "print \"(%d %cs)\" %c dir()n�", id_cast, '%', '%'); PyRun_SimpleString(command); Py_EndInterpreter(interpreter[id_cast]); printf("Thread %d ending n", id_cast); pthread_exit((void *) 0); }
El canvi substancial que he fet ha sigut pujar primerament la creació del Interpret a la funció del thread i seguidament utilizar exclusivament la funció PyEval_AdquireThread per personalitzar un thread de context python per a ell, finalment utilizo Py_EndInterpreter per destruir no solament l'interpret crear si no tots els treats associats a ell. Per tant no hem caldra utilizar la funció ReleaseThread.
També haureu notat el canvi en la prova de concurrencia, abans utilizava una crida des de C de la syscall sleep(5), ara el que faig es fer una crida de python del tipus "time.sleep(n)".
Mitjançant aquest canvi volia saber si un cop adquirit el GIL mitjaçant PyEval_AdquireThread si executava una ordre que produis un I/O bloquing aquest quedaria alliberat per a altres interprets. La resposta es si.
No cal anar a cercar molt lluny el perque, tal i com nosaltres varem poder implementar i usar les macros PY_BEGIN_ALLOW_THREADS i PY_END_ALLOW_THREADS, la implementació del modul time , si aquesta estigues escrita com una extencio amb C , també ho podria realitzar.
En cas contrari, si el modul time estigues escrit integrament amb python, el core d'aquest incorpora un sistema d'intercanvi de contextos de threads i alliberació del GIL automatic en cas d'executar-se més de 100 bytecode instructions o bé la predicció d'un llarg I/O bloquing.
Pero be tornant una mica a l'inici del post, parlava de que hi havia dos grups definits de funcions un anomenat d'alt nivell i un altre anomenat de baix nivell, aquest segon grup esta format per funcions del tipus PyThreadSate_Swap i d'altres. Es possible reconvertir l'exemple anterior amb aquestes funcions, pero aixo ja sera un altre dia.
Cal dir pero, i com ja diu el manual de Python, les primeres, les d'alt nivell son una abstracció de l'us de les de mes baix nivell.