<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Mundo Geek &#187; lock</title>
	<atom:link href="http://mundogeek.net/etiqueta/lock/feed/" rel="self" type="application/rss+xml" />
	<link>http://mundogeek.net</link>
	<description>Mundo geek, bitácora sobre todo lo geek: software, gadgets, tecnología, internet, ...</description>
	<lastBuildDate>Fri, 25 May 2012 14:51:34 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.2</generator>
		<item>
		<title>Threads en Python</title>
		<link>http://mundogeek.net/archivos/2008/04/18/threads-en-python/</link>
		<comments>http://mundogeek.net/archivos/2008/04/18/threads-en-python/#comments</comments>
		<pubDate>Fri, 18 Apr 2008 13:12:00 +0000</pubDate>
		<dc:creator>Zootropo</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[lock]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[queue]]></category>
		<category><![CDATA[semaforos]]></category>
		<category><![CDATA[threading]]></category>
		<category><![CDATA[threads]]></category>
		<category><![CDATA[tutorial]]></category>

		<guid isPermaLink="false">http://mundogeek.net/?p=1462</guid>
		<description><![CDATA[¿Qué son los procesos y los threads? Las computadoras serían mucho menos útiles si no pudiéramos hacer más de una cosa a la vez. Si no pudiéramos, por ejemplo, escuchar música en nuestro reproductor de audio favorito mientras leemos un tutorial de Python en Mundo Geek. Pero, ¿cómo se conseguía esto en computadoras antiguas con [...]]]></description>
			<content:encoded><![CDATA[<h2>¿Qué son los procesos y los threads?</h2>
<p>Las computadoras serían mucho menos útiles si no pudiéramos hacer más de una cosa a la vez. Si no pudiéramos, por ejemplo, escuchar música en nuestro reproductor de audio favorito mientras leemos un <a href="http://mundogeek.net/etiqueta/python+tutorial" title="Tutorial de Python">tutorial de Python en Mundo Geek</a>.</p>
<p>Pero, ¿cómo se conseguía esto en computadoras antiguas con un solo núcleo / una sola CPU? Lo que ocurría, y lo que ocurre ahora, es que en realidad no estamos ejecutando varios procesos a la vez (se llama proceso a un programa en ejecución), sino que los procesos se van turnando y, dada la velocidad a la que ejecutan las instrucciones, nosotros tenemos la impresión de que las tareas se ejecutan de forma paralela como si tuviéramos multitarea real.<br />
<span id="more-1462"></span><br />
Cada vez que un proceso distinto pasa a ejecutarse es necesario realizar lo que se llama un <em>cambio de contexto</em>, durante el cual se salva el estado del programa que se estaba ejecutando a memoria y se carga el estado del programa que va a entrar a ejecutarse.</p>
<p>En Python podemos crear nuevos procesos mediante la función <code>os.fork</code>, que ejecuta la llamada al sistema fork, o mediante otras funciones más avanzadas como <code>popen2.popen2</code>, de forma que nuestro programa pueda realizar varias tareas de forma paralela.</p>
<p>Sin embargo el cambio de contexto puede ser relativamente lento, y los recursos necesarios para mantener el estado demasiados, por lo que a menudo es mucho más eficaz utilizar lo que se conoce como threads, hilos de ejecución, o procesos ligeros.</p>
<p>Los threads son un concepto similar a los procesos: también se trata de código en ejecución. Sin embargo los threads se ejecutan dentro de un proceso, y los threads del proceso comparten recursos entre si, como la memoria, por ejemplo.</p>
<p>El sistema operativo necesita menos recursos para crear y gestionar los threads, y al compartir recursos, el cambio de contexto es más rápido. Además, dado que los threads comparten el mismo espacio de memoria global, es sencillo compartir información entre ellos: cualquier variable global que tengamos en nuestro programa es vista por todos los threads.</p>
<h2>El GIL</h2>
<p>La ejecución de los threads en Python está controlada por el GIL (Global Interpreter Lock) de forma que sólo un thread puede ejecutarse a la vez, independientemente del número de procesadores con el que cuente la máquina. Esto posibilita que el escribir extensiones en C para Python sea mucho más sencillo, pero tiene la desventaja de limitar mucho el rendimiento, por lo que a pesar de todo, en Python, en ocasiones nos puede interesar más utilizar procesos que threads, que no sufren de esta limitación.</p>
<p>Cada cierto número de instrucciones de bytecode la máquina virtual para la ejecución del thread y elige otro de entre los que estaban esperando.</p>
<p>Por defecto el cambio de thread se realiza cada 10 instrucciones de bytecode, aunque se puede modificar mediante la función <code>sys.setcheckinterval</code>. También se cambia de thread cuando el hilo se pone a dormir con <code>time.sleep</code> o cuando comienza una operación de entrada/salida, las cuales pueden tardar mucho en finalizar, y por lo tanto, de no realizar el cambio, tendríamos a la CPU demasiado tiempo sin trabajar esperando a que la operación de E/S terminara.</p>
<p>Para minimizar un poco el efecto del GIL en el rendimiento de nuestra aplicación es conveniente llamar al intérprete con el flag -O, lo que hará que se genere un bytecode optimizado con menos instrucciones, y, por lo tanto, menos cambios de contexto. También podemos plantearnos el utilizar procesos en lugar de threads, como ya comentamos, utilizando por ejemplo el módulo <code>processing</code>; escribir el código en el que el rendimiento sea crítico en una extensión en C o utilizar IronPython o Jython, que carecen de GIL.</p>
<h2>Threads en Python</h2>
<p>El trabajo con threads se lleva a cabo en Python mediante el módulo <code>thread</code>. Este módulo es opcional y dependiente de la plataforma, y puede ser necesario, aunque no es común, recompilar el intérprete para añadir el soporte de threads.</p>
<p>Además de <code>thread</code>, también contamos con el módulo <code>threading</code> que se apoya en el primero para proporcionarnos una API de más alto nivel, más completa, y orientada a objetos. El módulo <code>threading</code> se basa ligeramente en el modelo de threads de Java.</p>
<p>El módulo <code>threading</code> contiene una clase <code>Thread</code> que debemos extender para crear nuestros propios hilos de ejecución. El método <code>run</code> contendrá el código que queremos que ejecute el thread. Si queremos especificar nuestro propio constructor, este deberá llamar a <code>threading.Thread.__init__(self)</code> para inicializar el objeto correctamente.</p>
<pre name="code" class="python">import threading

class MiThread(threading.Thread):
      def __init__(self, num):
          threading.Thread.__init__(self)
          self.num = num

      def run(self):
          print "Soy el hilo", self.num</pre>
<p>Para que el thread comience a ejecutar su código basta con crear una instancia de la clase que acabamos de definir y llamar a su método <code>start</code>. El código del hilo principal y el del que acabamos de crear se ejecutarán de forma concurrente.</p>
<pre name="code" class="python">print "Soy el hilo principal"

for i in range(0, 10):
    t = MiThread(i)
    t.start()
    t.join()</pre>
<p>El método <code>join</code> se utiliza para que el hilo que ejecuta la llamada se bloquee hasta que finalice el thread sobre el que se llama. En este caso se utiliza para que el hilo principal no termine su ejecución antes que los hijos, lo cuál podría resultar en algunas plataformas en la terminación de los hijos antes de finalizar su ejecución. El método <code>join</code> puede tomar como parámetro un número en coma flotante indicando el número máximo de segundos a  esperar.</p>
<p>Si se intenta llamar al método <code>start</code> para una instancia que ya se está ejecutando, obtendremos una excepción.</p>
<p>La forma recomendada de crear nuevos hilos de ejecución consiste en extender la clase <code>Thread</code>, como hemos visto, aunque también es posible crear una instancia de <code>Thread</code> directamente, e indicar como parámetros del constructor una clase ejecutable (una clase con el método especial <code>__call__</code>) o una función a ejecutar, y los argumentos en una tupla (parámetro <code>args</code>) o un diccionario (parámetro <code>kwargs</code>).</p>
<pre name="code" class="python">import threading

def imprime(num):
    print "Soy el hilo", num

print "Soy el hilo principal"

for i in range(0, 10):
    t = threading.Thread(target=imprime, args=(i, ))
    t.start()</pre>
<p>Además de los parámetros <code>target</code>, <code>args</code> y <code>kwargs</code> también podemos pasar al constructor un parámetro de tipo cadena <code>name</code> con el nombre que queremos que tome el thread (el thread tendrá un nombre predeterminado aunque no lo especifiquemos); un parámetro de tipo booleano <code>verbose</code> para indicar al módulo que imprima mensajes sobre el estado de los threads para la depuración y un parámetro <code>group</code>, que por ahora no admite ningún valor pero que en el futuro se utilizará para crear grupos de threads y poder trabajar a nivel de grupos.</p>
<p>Para comprobar si un thread sigue ejecutándose, se puede utilizar el método <code>isAlive</code>. También podemos asignar un nombre al hilo y consultar su nombre con los métodos <code>setName</code> y <code>getName</code>.  </p>
<p>Mediante la función <code>threading.enumerate</code> obtendremos una lista de los objetos <code>Thread</code> que se están ejecutando, incluyendo el hilo principal (podemos comparar el objeto <code>Thread</code> con la variable <code>main_thread</code> para comprobar si se trata del hilo principal) y con <code>threading.activeCount</code> podemos consultar el número de threads ejecutándose.</p>
<p>Los objetos <code>Thread</code> también cuentan con un método <code>setDaemon</code> que toma un valor booleano indicando si se trata de un demonio. La utilidad de esto es que si solo quedan threads de tipo demonio ejecutándose, la aplicación terminará automáticamente, terminando estos threads de forma segura.</p>
<p>Por último tenemos en el módulo threading una clase <code>Timer</code> que hereda de <code>Thread</code> y cuya utilidad es la de ejecutar el código de su método <code>run</code> después de un periodo de tiempo indicado como parámetro en su constructor. También incluye un método <code>cancel</code> mediante el que cancelar la ejecución antes de que termine el periodo de espera. </p>
<h2>Sincronización</h2>
<p>Uno de los mayores problemas a los que tenemos que enfrentarnos al utilizar threads es la necesidad de sincronizar el acceso a ciertos recursos por parte de los threads. Entre los mecanismos de sincronización que tenemos disponibles en el módulo <code>threading</code> se encuentran los locks, locks reentrantes, semáforos, condiciones y eventos. </p>
<p>Los locks, también llamados mutex (de <em>mutual exclusion</em>), cierres de exclusión mutua, cierres o candados, son objetos con dos estados posibles: adquirido o libre. Cuando un thread adquiere el candado, los demás threads que lleguen a ese punto posteriormente y pidan adquirirlo se bloquearán hasta que el thread que lo ha adquirido libere el candado, momento en el cuál podrá entrar otro thread.</p>
<p>El candado se representa mediante la clase <code>Lock</code>. Para adquirir el candado se utiliza el método <code>acquire</code> del objeto, al que se le puede pasar un booleano para indicar si queremos esperar a que se libere (<code>True</code>) o no (<code>False</code>). Si indicamos que no queremos esperar, el método devolverá <code>True</code> o <code>False</code> dependiendo de si se adquirió o no el candado, respectivamente. Por defecto, si no se indica nada, el hilo se bloquea indefinidamente.</p>
<p>Para liberar el candado una vez hemos terminado de ejecutar el bloque de código en el que pudiera producirse un problema de concurrencia, se utiliza el método <code>release</code>.</p>
<pre name="code" class="python">lista = []

lock = threading.Lock()

def anyadir(obj):
    lock.acquire()
    lista.append(obj)
    lock.release()

def obtener():
    lock.acquire()
    obj = lista.pop()
    lock.release()
    return obj</pre>
<p>La clase <code>RLock</code> funciona de forma similar a <code>Lock</code>, pero en este caso el candado puede ser adquirido por el mismo thread varias veces, y no quedará liberado hasta que el thread llame a <code>release</code> tantas veces como llamó a <code>acquire</code>. Como en <code>Lock</code>, y como en todas las primitivas de sincronización que veremos a continuación, es posible indicar a <code>acquire</code> si queremos que se bloquee o no.</p>
<p>Los semáforos son otra clase de candados. La clase correspondiente, <code>Semaphore</code>, también cuenta con métodos <code>acquire</code> y <code>release</code>, pero se diferencia de un <code>Lock</code> normal en que el constructor de <code>Semaphore</code> puede tomar como parámetro opcional un entero <code>value</code> indicando el número máximo de threads que pueden acceder a la vez a la sección de código crítico. Si no se indica nada permite el acceso a un solo thread.</p>
<p>Cuando un thread llama a <code>acquire</code>, la variable que indica el número de threads que pueden adquirir el semáforo disminuye en 1, porque hemos permitido entrar en la sección de código crítico a un hilo más. Cuando un hilo llama a <code>release</code>, la variable aumenta en 1.</p>
<p>No es hasta que esta variable del semáforo es 0, que llamar a <code>acquire</code> producirá un bloqueo en el thread que realizó la petición, a la espera de que algún otro thread llame a <code>release</code> para liberar su plaza.</p>
<p>Es importante destacar que el valor inicial de la variable tal como lo pasamos en el constructor, no es un límite máximo, sino que múltiples llamadas a <code>release</code> pueden hacer que el valor de la variable sea mayor que su valor original. Si no es esto lo que queremos, podemos utilizar la clase <code>BoundedSemaphore</code> en cuyo caso, ahora si, se consideraría un error llamar a <code>release</code> demasiadas veces, y se lanzaría una excepción de tipo <code>ValueError</code> de superarse el valor inicial. </p>
<p>Podríamos utilizar los semáforos, por ejemplo, en un pequeño programa en el que múltiples threads descargaran datos de una URL, de forma que pudieramos limitar el número de conexiones a realizar al sitio web para no bombardear el sitio con cientos de peticiones concurrentes.</p>
<pre name="code" class="python">semaforo = threading.Semaphore(4)

def descargar(url):
    semaforo.acquire()
    urllib.urlretrieve(url)
    semaforo.release()</pre>
<p>Las condiciones (clase <code>Condition</code>) son de utilidad para hacer que los threads sólo puedan entrar en la sección crítica de darse una cierta condición o evento. Para esto utilizan un <code>Lock</code> pasado como parámetro, o crean un objeto <code>RLock</code> automaticamente si no se pasa ningún parámetro al constructor.</p>
<p>Son especialmente adecuadas para el clásico problema de productor-consumidor. La clase cuenta con métodos <code>acquire</code> y <code>release</code>, que llamarán a los métodos correspondientes del candado asociado. También tenemos métodos <code>wait</code>, <code>notify</code> y <code>notifyAll</code>.</p>
<p>El método <code>wait</code> debe llamarse después de haber adquirido el candado con <code>acquire</code>. Este método libera el candado y bloquea al thread hasta que una llamada a <code>notify</code> o <code>notifyAll</code> en otro thread le indican que se ha cumplido la condición por la que esperaba. El thread que informa a los demás de que se ha producido la condición, también debe llamar a <code>acquire</code> antes de llamar a <code>notify</code> o <code>notifyAll</code>.</p>
<p>Al llamar a <code>notify</code>, se informa del evento a un solo thread, y por tanto se despierta un solo thread. Al llamar a <code>notifyAll</code> se despiertan todos los threads que esperaban a la condición.</p>
<p>Tanto el thread que notifica como los que son notificados tienen que terminar liberando el lock con <code>release</code>.</p>
<pre name="code" class="python">lista = []
cond = threading.Condition()

def consumir():
    cond.acquire()
    cond.wait()
    obj = lista.pop()
    cond.release()
    return obj

def producir(obj):
    cond.acquire()
    lista.append(obj)
    cond.notify()
    cond.release()</pre>
<p>Los eventos, implementados mediante al clase <code>Event</code>, son un wrapper por encima de <code>Condition</code> y sirven principalmente para coordinar threads mediante señales que indican que se ha producido un evento. Los eventos nos abstraen del hecho de que estemos utilizando un <code>Lock</code> por debajo, por lo que carece de métodos <code>acquire</code> y <code>release</code>.</p>
<p>El thread que debe esperar el evento llama al método <code>wait</code> y se bloquea, opcionalmente pasando como parámetro un número en coma flotante indicando el número máximo de segundos a esperar. Otro hilo, cuando ocurre el evento, manda la señal a los threads bloqueados a la espera de dicho evento utilizando el método <code>set</code>. Los threads que estaban esperando se desbloquean una vez recibida la señal. El flag que determina si se ha producido el evento se puede volver a establecer a falso usando <code>clear</code>.</p>
<p>Como vemos los eventos son muy similares a las condiciones, a excepción de que se desbloquean todos los threads que esperaban el evento y que no tenemos que llamar a <code>acquire</code> y <code>release</code>.</p>
<pre name="code" class="python">import threading, time

class MiThread(threading.Thread):
      def __init__(self, evento):
          threading.Thread.__init__(self)
          self.evento = evento

      def run(self):
          print self.getName(), "esperando al evento"
          self.evento.wait()
          print self.getName(), "termina la espera"

evento = threading.Event()
t1 = MiThread(evento)
t1.start()
t2 = MiThread(evento)
t2.start()

# Esperamos un poco
time.sleep(5)
evento.set()</pre>
<p>Por último, un pequeño extra. Si sois usuarios de Java sin duda estaréis echando en falta una palabra clave <code>syncronized</code> para hacer que sólo un thread pueda acceder al método sobre el que se utiliza a la vez. Una construcción común es el uso de un decorador para implementar esta funcionalidad usando un <code>Lock</code>. Sería algo así:</p>
<pre name="code" class="python">def synchronized(lock):
    def dec(f):
        def func_dec(*args, **kwargs):
            lock.acquire()
            try:
                return f(*args, **kwargs)
            finally:
                lock.release()
        return func_dec
    return dec

class MyThread(threading.Thread):
    @synchronized(mi_lock)
    def run(self):
        print "metodo sincronizado"</pre>
<h2>Datos globales independientes</h2>
<p>Como ya hemos comentado los threads comparten las variables globales. Sin embargo pueden existir situaciones en las que queramos utilizar variables globales pero que estas variables se comporten como si fueran locales a un solo thread. Es decir, que cada uno de los threads tengan valores distintos independientes, y que los cambios de un determinado thread sobre el valor no se vean reflejados en las copias de los demás threads.</p>
<p>Para lograr este comportamiento se puede utilizar la clase <code>threading.local</code>, que crea un almacén de datos locales. Primero debemos crear una instancia de la clase, o de una subclase, para después almacenar y obtener los valores a través de parámetros de la clase.  </p>
<pre name="code" class="python">datos_locales = threading.local()
datos_locales.mi_var = "hola"
print datos_locales.mi_var</pre>
<p>Fijémonos en el siguiente código, por ejemplo. Para el hilo principal el objeto <code>local</code> tiene un atributo <code>var</code>, y por lo tanto el <code>print</code> imprime su valor sin problemas. Sin embargo para el hilo <code>t</code> ese atributo no existe, y por lo tanto lanza una excepción.</p>
<pre name="code" class="python">local = threading.local()

def f():
    print local.var

local.var = "hola"
t = threading.Thread(target=f)
print local.var
t.start()
t.join()</pre>
<h2>Compartir información</h2>
<p>Para compartir información entre los threads de forma sencilla podemos utilizar la clase <code>Queue.Queue</code>, que implementa una cola (una estructura de datos de tipo FIFO) con soporte multihilo. Esta clase utiliza las primitivas de <code>threading</code> para ahorrarnos tener que sincronizar el acceso a los datos nosotros mismos.</p>
<p>El constructor de <code>Queue</code> toma un parámetro opcional indicando el tamaño máximo de la cola. Si no se indica ningún valor no hay límite de tamaño.</p>
<p>Para añadir un elemento a la cola se utiliza el método <code>put(item)</code>; para obtener el siguiente elemento, <code>get()</code>. Ambos métodos tienen un parámetro booleano opcional <code>block</code> que indica si queremos que se espere hasta que haya algún elemento en la cola para poder devolverlo o hasta que la cola deje de estar llena para poder introducirlo.</p>
<p>También existe un parámetro opcional <code>timeout</code> que indica, en segundos, el tiempo máximo a esperar. Si el timeout acaba sin poder haber realizado la operación debido a que la cola estaba llena o vacía, o bien si <code>block</code> era <code>False</code>, se lanzará una excepción de tipo <code>Queue.Full</code> o <code>Queue.Empty</code>, respectivamente. </p>
<p>Con <code>qsize</code> obtenemos el tamaño de la cola y con <code>empty()</code> e <code>full()</code> podemos comprobar si está vacía o llena.</p>
<pre name="code" class="python">q = Queue.Queue() 

class MiThread(threading.Thread):
    def __init__(self, q):
        self.q = q
        threading.Thread.__init__(self)

    def run(self):
        while True:
            try:
                obj = q.get(False)
            except Queue.Empty:
                print "Fin"
                break
            print obj

for i in range(10):
    q.put(i)

t = MiThread(q)
t.start()
t.join()</pre>
<link type="text/css" rel="stylesheet" href="http://mundogeek.net/sh/css/SyntaxHighlighter.css"></link>
<script language="javascript" src="http://mundogeek.net/sh/js/shCore.js"></script><br />
<script language="javascript" src="http://mundogeek.net/sh/js/shBrushPython.js"></script><br />
<script language="javascript">dp.SyntaxHighlighter.ClipboardSwf = 'http://mundogeek.net/sh//flash/clipboard.swf';
dp.SyntaxHighlighter.HighlightAll('code');</script></p>
]]></content:encoded>
			<wfw:commentRss>http://mundogeek.net/archivos/2008/04/18/threads-en-python/feed/</wfw:commentRss>
		<slash:comments>24</slash:comments>
		</item>
	</channel>
</rss>

