El problema de la multi concurrència amb Python
June 8, 2008 – 2:16 pmDes de fa ja uns quants anys s'estan imposant les arquitectures multi core en els ordinadors de consum. Aquest fet està ajudant a que molts dels desenvolupadors es preocupin per conceptes com la multi concurrència.
Mitjançant aquesta suport a la multi concurrència, dotada pels propis nuclis dels processadors, el programa pot efectuar tasques en paral.lel. Existeixen diverses metodologies de procediment algorítmic per a aconseguir aquesta multi concurrència, però totes elles es basen en el concepte de procés lleuger o thread.
Un procés pot estar format per varis threads. Tots els threads d'un mateix procés comparteixen espai d'adreces i tenen un segment de pila independent.
Els sistemes multi core actuals tenen la capacitat d'executar diferents fils d'un mateix procés en un moment determinat mitjançant l'assignació de diferents CPUs a cada un dels fils. És responsabilitat del programador però poveïr els mecanismes adequats per evitar accessos simultanis a segments de dades que aquests comparteixen.
Python, com molts altres llenguatges, dona accés a la possibilitat de crear i manejar threads mitjançant el modul threading. Aquest mòdul és una capa d'abstracció del built-in threading del propi cpython i que utilitza la implementació nativa del sistema operatiu. En cas de Python sobre Sistemes Operatius gust Linux aquest mòdul està implementat mitjançant les llibreries pthread.
El codi següent mostra com mitjançant el mòdul threading podem crear dos fils en un mateix procés Python.
#!/usr/bin/python import thread import time NUM_THREADS = 2 def foo(arg): print "I'm a thread %d" % thread.get_ident() if __name__ == "__main__": for i in range(0, NUM_THREADS): thread.start_new_thread(foo, (None,)) # join thread hardcode time.sleep(2)
Python implementa internament el que s'anomena Global Internal Lock, GIL, per a poder donar suport a múltiples fils sobre un mateix procés. Aquest mecanisme actua de forma semblant a un semàfor global davant de múltiples fils d'un mateix procés. Què significa això ? fem una ullada al següent codi:
#!/usr/bin/python import thread import time NUM_THREADS = 2 def foo(arg): del(arg[0]) print "I'm a thread %d" % thread.get_ident() if __name__ == "__main__": a = [ "hellow", "world" ] for i in range(0, NUM_THREADS): thread.start_new_thread(foo, (a,)) # join thread hardcode time.sleep(2)
Aquesta nova versió del programar modifica els paràmetres que es transmeten des del programa principal a la funció foo i com els threads els reben. Ara cada thread obtindrà momentàniament una referència a la llista [ "hellow", "world" ] per eliminar-la posteriorment amb la funció del. El recolector de brosa utilitza les referencies a objectes com un mecanisme de traçabilitat per eliminar objecte de la memòria.
Mitjançant el GIL Python pot prevenir que dos fils intentin efectuar una operació atómica sobre un mateix objecte a la vegada. En l'exemple que presetem el GIL evita que els dos threads que utilitzen la funció del decrementin simultàniament les referències a la llista [ "hellow", "world" ]. Aquest mecanisme però, no evita que el propi programador eludeixi la responsabilitat de crear les zones d'exclusió mútua pertinents.
El gran problema d'aquest mecanisme és la problemàtica que es genera en entorns multi core, en el següent gràfic podem observar aquesta problemàtica:

Aquest gràfic mostra com el GIL impedeix que les dues CPUs puguin executar de forma simultània dos threads que pertanyen a un mateix procés de Python. Cada un d'ells bloqueja l'altre mitjançant l'adquisició del GIL. Aquest serà el comportament habitual entre threads d'un mateix procés python i no podran explotar les múltiples CPUs d'un sistema.
Python però implementa diferents mecanismes a la seva màquina virtual per evitar l'starvation de la CPU, on un sol thread acapari de forma permanent la CPU. Un dels mecanismes utilitzats es la auto cessió de la CPU per part d'un fil cada n instruccions bytecode.
Existeixen implementacions lliures que ja eviten aquest comportament tan desagradable, pero desconec de moment quines són les prespectives per a les noves versions de Python