La Gaceta de Linux ...¡ haciendo a Linux un poco más divertido !

Cómo funcionan los hilos de ejecución en Python

Por Krishna G Pai

Traducción al español por Victor Reyes
el día 17 de Marzo de 2005, para La Gaceta de Linux

Cuando se programa en cualquier lenguaje, la capacidad de expansión de los hilos de ejecución es integral al desempeño de cualquier aplicación, aunque se este ejecutando un hilo por separado para manejar la integración de una aplicación en una interfaz gráfica de usuario, mientras se ejecuta un posible bloqueador de procesos en segundo plano (como su navegador lo hace ahora), los hilos de ejecución son esenciales. Este documento intenta demostrar lo que es posible y lo que no, mientras se trabaja con hilos de ejecución en Python.

1.¿Porqué usar hilos de ejecución en Python?

Digamos que escribes en Python una utilería muy buena que te deja filtrar tu correo.

Construyes una interfaz gráfica usando PyGTK, ahora si metes el código del filtro en la interfaz, arriesgas, haciendo que la aplicación no responda (todavía tienes una conexión por marcación telefónica, y cualquier interacción con el servidor exige un tiempo de espera considerable). Puesto que no trabajas en Microsoft, decides que esto es inaceptable y comienzas así un hilo de ejecución separado cada vez que quieres filtrar tu correo.

Así los hilos de ejecución aumentan la respuesta de tus programas. Los hilos de ejecución también aumentan la eficacia y velocidad de un programa, sin mencionar la simplicidad algorítmica.

Así es que combinado con la potencia de Python, esto hace muy atractiva la programación en Python.

2.Los Fundamentos

Primeramente veamos como iniciar un simple hilo de ejecución. La ejecución de hilos es soportado vía los módulos thread y threading. Supuestamente estos módulos son opcionales, pero si usas un Sistema Operativo que no soporte hilos de ejecución, mejor cambia a Linux.

El código mencionado abajo ejecuta un hilo de ejecución simple en segundo plano. (Versión Texto)

#!/usr/bin/env python

import time
import thread

def myfunction(string,sleeptime,*args):
    while 1:
       
        print string
        time.sleep(sleeptime) #dormir por cierta cantidad de tiempo.

if __name__=="__main__":

    thread.start_new_thread(myfunction,("Thread No:1",2))

    while 1:pass

Iniciamos un nuevo hilo de ejecución usando la función start_new_thread() la cual toma la dirección del objeto que se ejecuta, junto con los argumentos que son pasados al objeto, los cuales son pasados a un conjunto.

2.1 Bloqueos [Locks]

Ahora que tenemos un hilo de ejecución trabajando, hacer trabajar varios hilos de ejecución es tan simple como llamar a la función start_new_thread() varias veces. El problema ahora sería sincronizar varios hilos que estuviéramos ejecutando. La sincronización es hecha usando el objeto Lock. Locks son creados usando la función de creación allocate_lock().

Los Locks son usados como un objeto mutuo de exclusión, y son usados para el manejo critico de secciones de código. Un hilo de ejecución entra a la sección crítica llamando al método acquire() el cual puede ya ser bloqueador o no bloqueador. Un hilo de ejecución sale de la sección crítica que es llamado por el método release().

El siguiente listado muestra como se usa el objeto Lock. (Versión Texto)

#!/usr/bin/env python

import time
import thread

def myfunction(string,sleeptime,lock,*args):
    while 1:
	#entering critical section
        lock.acquire() 
        print string," Now Sleeping after Lock acquired for ",sleeptime
        time.sleep(sleeptime) 
        print string," Now releasing lock and then sleeping again"
        lock.release()
	#exiting critical section
        time.sleep(sleeptime) # why?

if __name__=="__main__":

    lock=thread.allocate_lock()
    thread.start_new_thread(myfunction,("Thread No:1",2,lock))
    thread.start_new_thread(myfunction,("Thread No:2",2,lock))

    while 1:pass

El código mostrado arriba es muy claro. Llamamos a lock.acquire() justo antes de entrar a la sección crítica y después llamamos a lock.release() para salir de la sección crítica.

El lector curioso se preguntara el porqué enviamos a dormir [sleep] a el proceso después de salir de la sección crítica.

Examinemos la salida del listado arriba mencionado.

Salida.


Thread No:2  Now Sleeping after Lock acquired for  2
Thread No:2  Now releasing lock and then sleeping again
Thread No:1  Now Sleeping after Lock acquired for  2
Thread No:1  Now releasing lock and then sleeping again
Thread No:2  Now Sleeping after Lock acquired for  2
Thread No:2  Now releasing lock and then sleeping again
Thread No:1  Now Sleeping after Lock acquired for  2
Thread No:1  Now releasing lock and then sleeping again
Thread No:2  Now Sleeping after Lock acquired for  2

Aquí a cada hilo de ejecución se le da una oportunidad de entrar en la sección critica. Pero el mismo no puede decir si es retirado por time.sleep(sleeptime) del listado arriba mencionado.

Salida sin time.sleep(sleeptime)


Thread No:1  Now Sleeping after Lock acquired for  2
Thread No:1  Now releasing lock and then sleeping again
Thread No:1  Now Sleeping after Lock acquired for  2
Thread No:1  Now releasing lock and then sleeping again
Thread No:1  Now Sleeping after Lock acquired for  2
Thread No:1  Now releasing lock and then sleeping again
Thread No:1  Now Sleeping after Lock acquired for  2
Thread No:1  Now releasing lock and then sleeping again
Thread No:1  Now Sleeping after Lock acquired for  2
Thread No:1  Now releasing lock and then sleeping again
Thread No:1  Now Sleeping after Lock acquired for  2
Thread No:1  Now releasing lock and then sleeping again
Thread No:1  Now Sleeping after Lock acquired for  2
Thread No:1  Now releasing lock and then sleeping again
Thread No:1  Now Sleeping after Lock acquired for  2

¿Porqué pasa esto? La respuesta esta en el hecho de que Python no es completamente seguro para la ejecución de hilos. No como Java, donde los hilos de ejecución son considerados tan importantes que son parte de la sintaxis, en Python los hilos de ejecución fueron colocados en el altar de la Portabilidad.

De hecho la documentación lee:

  • No todas las funciones incluidas que puedan bloquear la espera para la E/S permiten a otros hilos ser ejecutados. (Los mas populares time.sleep(), file.read(), select.select() trabajan según lo esperado.
  • No es posible interrumpir el método acquire() en un bloqueo de excepción KeyboardInterrupt sucederá después de que se haya obtenido el bloqueo.

Lo que esto significa es que muy probablemente cualquier código como el siguiente:

while 1:
	lock.acquire()
	.....
	#some operation
	.....
	lock.release()

Causará el consumo excesivo de recursos de uno o más hilos de ejecución.

3. El interprete global de bloqueo

Actualmente, el interprete Python (Python 2.3.4) no es seguro para la ejecución de hilos. No hay prioridades, ni grupos de hilos de ejecución. Los hilos de ejecución no pueden ser detenidos y suspendidos, resumidos o interrumpidos es decir, el soporte proporcionado es muy básico, no obstante se puede lograr mucho con este pobre soporte, con el uso del módulo threading, como veremos en las siguientes secciones. Una de las principales razones es que en la actualidad solamente un hilo es ejecutado a la vez, esto es porque algo llamado Interprete Global de Bloqueo (GIL) [Global Interpreter Lock]. En orden para dar el soporte a los programas de multi-hilos de ejecución en Python, hay un bloqueo global que tiene que ser retenido por el actual hilo de ejecución antes de pueda acceder a los objetos de Python de forma segura. Sin el bloqueo la disputa de los hilos de ejecución podrían causar estrago, por ejemplo: cuando dos hilos de ejecución incrementan el conteo del mismo objeto simultáneamente, la referencia del conteo podría terminar siendo incrementado solamente una vez en lugar de dos veces. De este modo solamente el hilo de ejecución que ha obtenido el GIL pude operar en los Objetos de Python o llamar las funciones de la API C de Python.

En orden para dar el soporte a los multi hilos de ejecución en programas de Python el interprete regularmente libera y retoma el bloqueo, predeterminadamente cada 10 bytecode de instrucciones. Sin embargo esto puede cambiar usando la función sys.setcheckinterval(). El bloqueo es también liberado y retomado bloqueando probablemente las operaciones de E/S como es leyendo o escribiendo un archivo, para que otros hilos puedan ser ejecutados mientras el hilo de ejecución que pide la E/S en la espera de la operación de E/S que sea completada.

Note en particular:

  • Extensiones de C pueden liberar el GIL.
  • Bloqueando la E/S puede liberar el GIL.

El interprete de Python mantiene almacenada la información por hilo de ejecución, para lo cual usa una estructura de datos llamada PyThreadState. Anteriormente el estado era almacenada en variables globales y cambiando los hilos de ejecución que pudieran causar problemas. En particular, el manejo de excepción que es ahora seguro para la ejecución de hilos cuando la aplicación usa sys.exc_info() para acceder a la ultima excepción establecida en el la ejecución del hilo actual. Hay una variable global que queda, el indicador a la estructura actual PyThreadState. Mientras muchos de los paquetes de los hilos de ejecución tienen una forma para almacenar ``datos globales por-hilo de ejecución'' la abstracción independiente del hilo de ejecución de la plataforma interna de Python no soporta esto todavía. Por lo tanto, el estado actual del hilo de ejecución debe ser manipulado explícitamente, el bloqueo global del intérprete se utiliza para proteger al indicador de estado actual del hilo de ejecución. Al liberar el bloqueo y guardar el estado del hilo de ejecución, el indicador actual del estado del hilo de ejecución debe ser recuperado antes de que se libere el bloqueo (como otro hilo de ejecución podrían obtener inmediatamente el bloqueo y almacenar su propio estado del hilo de ejecución en la variable global). Inversamente, al obtener el bloqueo y restaurar el estado del hilo de ejecución, el bloqueo se debe obtener antes de almacenar el indicador del estado del hilo de ejecución.


  ------------------------------------------------------------------
  Global Thread | Global Thread  | Global Thread  | Global Thread  | 
  Pointer 1     | Pointer 2      | Pointer 2      | Pointer 2      |
  ------------------------------------------------------------------
        ^            ^               ^                ^
	|            |               |                |
	|            |               |                |
	|            |               |                |
	------------------------------------------------
	|                                              |
	|            Global  Interpreter  Lock         |
	|                                              |
	------------------------------------------------
	 ^         ^            ^           ^      
	 |         |            |           |  
	 |         |            |           |
	Thread No   Thread No    Thread No  Thread No
	    1           2           3          4   
         
   

4.Utilizando el módulo de hilos de ejecución

Python hace mucho utilizando tan poco. El módulo de hilos usa el paquete incorporado que proporcionar algunas características muy interesantes que harán tu programación mucho más fácil. Hay mecanismos incorporados que proporcionan el bloqueo de sección crítica, espera/notificación etc. En particular debemos ver:

  • Usar el Objeto del hilo de ejecución
  • Evaluar el código de los hilos de ejecución
  • Usar la Condición, Evento, y Objeto de Cola

4.1 Utilizando la biblioteca de los hilos de ejecución

Los componentes principales del módulo de los hilos de ejecución son:

  • Objeto de Bloqueo [Lock]
  • Objeto RLock
  • Objeto del Semáforo [Semaphore]
  • Objeto de la Condición [Condition]
  • Objeto del evento [Event]
  • Objeto del hilo de ejecución [Thread]

Mientras que hemos visto el objeto Lock en las secciones anteriores, el objeto RLock es algo nuevo. RLock proporciona un mecanismo de un hilo de ejecución para obtener múltiples casos del mismo bloqueo, incrementando cada vez la profundidad del bloqueo cuando se obtiene y decrementa la profundidad del bloqueo cuando se libera. RLock hace muy fácil el escribir el código que se conforma con el problema clásico de los Escritores Lectores. El objeto del Semaphore (mejor dicho el Objeto del Semáforo de creación) es la implementación en la práctica general del semáforo discutido por Dijikstra, entenderemos la implementación en la práctica de los objetos de la condición, del evento y del hilo de ejecución vía algunos ejemplos.

4.2 Objeto del hilo de ejecución

El Objeto del hilo de ejecución es una envoltura para la función start_new_thread(), la cual vimos anteriormente, pero con un poco mas de funcionalidad. El Objeto del hilo de ejecución nunca es usado directamente, solamente por la interfaz de subclase threading.Thread. Entonces el usuario supuestamente tiene que reescribir la función de __init__() o de run(). No reescribir la función de start(), o proporciones más de un argumento a run. Observa que supuestamente tienes que llamar a Thread.__init__() si está reescribiendo __init__().

Veamos un simple ejemplo:

#!/usr/bin/env python
#codigo simple que usa ejecución de hilos

import time
from threading import Thread

class MyThread(Thread):

    def __init__(self,bignum):

        Thread.__init__(self)
        self.bignum=bignum
    
    def run(self):

        for l in range(10):
            for k in range(self.bignum):
                res=0
                for i in range(self.bignum):
                    res+=1


def test():
    bignum=1000
    thr1=MyThread(bignum)
    thr1.start()
    thr1.join()
    
if __name__=="__main__":
    test()

Hay 2 cosas que observar aquí, el hilo de ejecución no inicia hasta que el método start() es llamado, y que join() haga la llamada al hilo de ejecución que espera hasta que el hilo de ejecución haya terminado.

¡Hasta ahora todo bien! Sin embargo siendo siempre curiosos no preguntamos si hay algún desempeño obtenido usando los hilos de ejecución.

4.3 Evaluar el código de los hilos de ejecución.

Es la práctica de un muy buen programador evaluar su código, para encontrar sus puntos débiles, sus fortalezas, y en general saber su alma interior ;-). Y como estamos tratando con el Tao de los hilos de ejecución en Python, puede ser que nos preguntemos ¿cuál es el más rápido?, ¿dos hilos de ejecución compartiendo la carga o uno resistente a la fuerza bruta?


¿Cuál es más rápido? 
 
  thread1                                   thread2
  --------                                  ---------
  
  for i in range(bignum):                   for i in range(bignum):
    for k in range(bignum):                   for k in range(bignum):
         res+=i                                    res+=i            

or?

        thread 3  
--------------------
for i in range(bignum):                 
    for k in range(bignum):            
       res+=i
	      
for i in range(bignum):                 
    for k in range(bignum):            
       res+=i	

Siguiendo el camino de los maestros no hacemos ninguna aceptación y dejemos que el código hable por si mismo. Generalmente hay 2 maneras de evaluar el código en Python, la manera más común y más comprensiva sería utilizar el método profile.run(), o tomar el tiempo de la ejecución del código usando time.clock(). Haremos ambos. Considera el listado mostrado abajo.

#!/usr/bin/env python

#Evaluemos el codigo que usa hilos de ejecución
import time
from threading import Thread

class MyThread(Thread):

    def __init__(self,bignum):

        Thread.__init__(self)
        self.bignum=bignum
    
    def run(self):

        for l in range(10):
            for k in range(self.bignum):
                res=0
                for i in range(self.bignum):
                    res+=1


def myadd_nothread(bignum):

    for l in range(10):
        for k in range(bignum):
            res=0
            for i in range(bignum):
                res+=1

    for l in range(10):
        for k in range(bignum):
            res=0
            for i in range(bignum):
                res+=1

def thread_test(bignum):
    #We create 2 Thread objects  for the 2 threads.
    thr1=MyThread(bignum)
    thr2=MyThread(bignum)

    thr1.start()
    thr2.start()

    thr1.join()
    thr2.join()
    

def test():

    bignum=1000

    #Probemos la parte de los hilos de ejecución

    starttime=time.clock()
    thread_test(bignum)
    stoptime=time.clock()

    print "Running 2 threads took %.3f seconds" % (stoptime-starttime)
    
    #Ahora ejecutemos sin hilos de ejecución
    starttime=time.clock()
    myadd_nothread(bignum)
    stoptime=time.clock()

    print "Running Without Threads took %.3f seconds" % (stoptime-starttime)


if __name__=="__main__":

    test()

Evaluando el código del hilo ejecutado en Python

Conseguimos algunos resultados sorprendentes, notablemente el siguiente..

Running 2 threads took 0.000 seconds
Running Without Threads took 5.160 seconds

Siendo siempre escépticos probaremos evaluando el código del hilo ejecutando, profile.run('test() '). Lo que vemos se parece agregar confianza a los resultados logrados anteriores.

       42 function calls in 5.170 CPU seconds
 
   Ordered by: standard name
 
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    5.170    5.170 :1(?)
        2    0.000    0.000    0.000    0.000 prof3.py:10(__init__)
        1    5.170    5.170    5.170    5.170 prof3.py:24(myadd_nothread)
        1    0.000    0.000    0.000    0.000 prof3.py:38(thread_test)
        1    0.000    0.000    5.170    5.170 prof3.py:50(test)
        1    0.000    0.000    5.170    5.170 profile:0(prof3.test())
        0    0.000             0.000          profile:0(profiler)
        2    0.000    0.000    0.000    0.000 threading.py:147(Condition)
        2    0.000    0.000    0.000    0.000 threading.py:152(__init__)
        1    0.000    0.000    0.000    0.000 threading.py:180(_release_save)
        1    0.000    0.000    0.000    0.000 threading.py:183(_acquire_restore)
        1    0.000    0.000    0.000    0.000 threading.py:186(_is_owned)
        1    0.000    0.000    0.000    0.000 threading.py:195(wait)
        2    0.000    0.000    0.000    0.000 threading.py:356(_newname)
        2    0.000    0.000    0.000    0.000 threading.py:373(__init__)
        2    0.000    0.000    0.000    0.000 threading.py:387(_set_daemon)
        4    0.000    0.000    0.000    0.000 threading.py:39(__init__)
        2    0.000    0.000    0.000    0.000 threading.py:402(start)
        6    0.000    0.000    0.000    0.000 threading.py:44(_note)
        2    0.000    0.000    0.000    0.000 threading.py:468(join)
        2    0.000    0.000    0.000    0.000 threading.py:507(isDaemon)
        5    0.000    0.000    0.000    0.000 threading.py:609(currentThread)

¡Como dije parece dar confianza, hasta que notamos que ni siquiera el run() es llamado! ¿Esto que nos enseña? Aparte de la desconfianza del evaluador, nunca sigas ninguna manera mecánica de probar tu código. La razón por la que la salida es engañosa y el evaluador silencioso es porque ambos el time.clock() y el evaluador toman el tiempo usado en la ejecución del hilo de ejecución actual.

¿Entonces como medimos el tiempo tomado por los dos hilos de ejecución? Utilizamos la función time.time().

Te preguntas, ¿Pero no time.time() nos da el tiempo absoluto? ¿Que hay de los interruptores del Contexto? Es verdad, pero como estamos interesados solamente en la medida del tiempo total que se tomo y no del trabajo de distribución, podemos ignorar el contexto de los interruptores (y de hecho el código se ha estructurado para no hacer caso a los interruptores del contexto)

La forma correcta de evaluar el código es mencionado en el siguiente listado: La Forma Correcta de Evaluar el Código.

Los resultados que obtenemos ahora son mas reales y confiables.

Running 2 threads took 5.125 seconds
Running Without Threads took 5.137 seconds

Como podemos ver no hay una diferencia significativa entre las aplicaciones hiladas y las no hiladas.

4.4 Condición, Evento y Objeto de Cola.

Las Condiciones son una forma de sincronizar el acceso entre varios hilos de ejecución, los cuales esperan una condición en particular que sea verdadera para iniciar cualquier proceso importante. Las Condiciones de Objetos son un mecanismo muy elegante por las cuales es posible implantar el Problema del Consumidor del Producto. Esto es de hecho verdad para cualquier cosa en Python, las condiciones toman un objeto de Lock. o si ninguno es proporcionado crea su propio objeto RLock. El hilo de ejecución espera por una condición en particular a ser verdadera usando la función wait(), mientras le da la señal a otro hilo de ejecución usando el método notify() o notifyAll().

Veamos como es resuelto usando el clásico Problema del Consumidor Productor.

#!/usr/bin/env python

#Evaluemos el codigo que usa hilos de ejecución
import thread
import time
from threading import *

class itemQ:

    def __init__(self):
        self.count=0

    def produce(self,num=1):
        self.count+=num

    def consume(self):
        if self.count: self.count-=1

    def isEmpty(self):
        return not self.count


class Producer(Thread):

    def __init__(self,condition,itemq,sleeptime=1):
        Thread.__init__(self)
        self.cond=condition
        self.itemq=itemq
        self.sleeptime=sleeptime

    def run(self):
        cond=self.cond
        itemq=self.itemq

        while 1 :
            
            cond.acquire() #acquire the lock
            print currentThread(),"Produced One Item"
            itemq.produce()
            cond.notifyAll()
            cond.release()

            time.sleep(self.sleeptime)


class Consumer(Thread):

    def __init__(self,condition,itemq,sleeptime=2):
        Thread.__init__(self)
        self.cond=condition
        self.itemq=itemq
        self.sleeptime=sleeptime

    def run(self):
        cond=self.cond
        itemq=self.itemq

        while 1:
            time.sleep(self.sleeptime)
            
            cond.acquire() #acquire the lock
            
            while itemq.isEmpty():
                cond.wait()
                
            itemq.consume()
            print currentThread(),"Consumed One Item"
            cond.release()
        
        

        
if __name__=="__main__":

    q=itemQ()

    cond=Condition()

    pro=Producer(cond,q)
    cons1=Consumer(cond,q)
    cons2=Consumer(cond,q)

    pro.start()
    cons1.start()
    cons2.start()
    while 1: pass

Listado del Productor Consumidor en Python

Aquí la función currentThread() regresa el id de la ejecución del hilo de ejecución actual. Observa que la función wait()tiene un argumento opcional especificando los segundos que tiene que esperar antes de expirar. Desalentaría este uso porque utilizaron un mecanismo de búsqueda para poner esto en ejecución, y según el código fuente hacemos la búsqueda al menos 20 veces cada segundo. Quisiera de nuevo precisar como no utilizar el Objeto de la Condición. Considera el siguiente ejemplo prestado de Python-2.3.4/Libs/threading.py

 
def notify(self, n=1):
        currentThread() # para efectos secundarios
        assert self._is_owned(), "notify() of un-acquire()d lock"
        __waiters = self.__waiters
        waiters = __waiters[:n]
        if not waiters:
            if __debug__:
                self._note("%s.notify(): no waiters", self)
            return
        self._note("%s.notify(): notifying %d waiter%s", self, n,
                   n!=1 and "s" or "")
        for waiter in waiters:
            waiter.release()
            try:
                __waiters.remove(waiter)
            except ValueError:
                pass
                                                                                       
    def notifyAll(self):
        self.notify(len(self.__waiters))
                                                                                       
Python-2.3.4/Lib/threading.py 
Lo que threading.py hace es mantener a la lista de los hilos de ejecución esperando en la actual condición. Después intenta notificarlos quitando los primeros n que esperan en la lista. Y como estos son removidos los mismos primeros n esperan cada vez, esto puede causar posiblemente el consumo de recursos de ciertos hilos de ejecución. Así que para probar nuestra teoría, notifiquemos a el Consumidor Productor mencionado en el listado de arriba haciendo el los siguientes cambios:
cons1=Consumer(cond,q,sleeptime=1)
cons2=Consumer(cond,q,sleeptime=1)
Posiblemente esto puede causar el consumo excesivo de uno de los hilos de ejecución dependiendo de como son insertados dentro de la lista, de hecho una prueba de ejecución haciendo los cambios mostrados arriba será el caso. Así que tendrás que ser cuidadoso de las posibles trampas al usar los hilos de ejecución en Python. Nota que no hay ningún punto en llamar a notifyAll() puesto que es ineficaz y debe ser evitado.

Ahora debes tener una muy buena idea sobre como programar hilos de ejecución en Python. Para terminar describiré brevemente el Evento y los Objetos de Cola y como usarlos.

El Objeto del Evento es realmente una delgada envoltura en el Objeto de la Condición de modo que no tenemos que enredarnos con los bloqueos. Los métodos proporcionados son muy explicativos: set() clear() isSet() wait(timeout). Una cosa que hay que notar es que los Objetos del Evento usan notifyAll(), así lo utiliza solamente cuando es necesario.

El código mencionado es un simple ejemplo del Objeto del Evento. event.py

Aunque las Colas no vienen bajo el modulo de los Hilos de ejecución, proporcionan una interfaz fácil que debe ser conveniente para resolver la mayoría de los problemas. La ventaja principal de la Cola es que esta no es implementada en el módulo threading. Así que puedes usarlo en lugar de otro con el módulo thread. Las Colas son una manera simple y eficiente de implementar el apilado de la prioridad de la cola etc. Así que maneja la protección y la sincronización de los datos. Los métodos usados son put(item,block) get(block) Queue(maxsize) qsize() empty() full().

Un simple ejemplo usando las Colas se da en el listado siguiente. q.py


Referencias

Sugiero la lectura de lo siguiente para mas información:


Happy Hacking ! ;-}

 


[BIO] Soy un entusiasta de Linux que vive en la India. Me encanta jugar con Linux sin saber que pueda descubrir. Disfruto de la libertad y el poder que Linux ofrece y se la debo agradecer a mi mentor Mr.Pramode C.E (quien publica regularmente en LG) por introducirme a las maravillosas oportunidades que Linux ofrece.

Siempre he disfrutado programar, y disfrutado mucho mas en Linux. (no mencionare mis SO anteriores :-) ). El editor Emacs me tiene enganchado. ¿Es un compilador?, ¿Editor? ¿Herramienta del AI? (No, es super-macs, :-) )

Programo un poco en AI, especialmente Redes Neuronales, y espero liberar una Caja de Herramientas de Red Neuronal que un eventual, Hardware hacker pueda hace uso de alguna cosa interesante.

Copyright © 2004, Suramya Tomar. Publicado bajo Open Publication license

Publicado en número 107 de Linux Gazette, Octubre 2004