Aleatoriedad y timing desde python: multithreading.

Muy buenas,

Hoy se me va a ir un poco la pinza y voy a hablar de cosas molonas como la aleatoriedad (basada en el tiempo) y os voy a mostrar un caso concreto en el que la veréis.

En este caso el script puede ser válido para utilizar como plantilla para usar multithreading en python. Yo lo hice para eso, para hacer unas primeras pruebas.

Os cuento un poco cómo funciona y luego explico lo interesante del tema.
Bueno, en este caso tiramos de programación orientada a objetos (recordemos que esto es para probar plantillas de hilos 😉 ), se hereda la clase thread
que tiene todo lo que necesitamos para crear hilos. La clase está más diseñada para crear consumidores o trabajadores, por lo que para hacer que el hilo sea un bucle infinito hay que crear un Event que facilite la detención, que sería el stoprequest. Cuando se setee este evento el bucle infinito se saltará y el hilo terminará. Para setearlo, hemos añadido ese set a la función join que es la que pide al hilo principal esperar a que el otro termine.

En la clase Hilo, las cosas ocurrirán en este orden:

  1. __init__: que inicializa el hilo.
  2. run: que ejecuta la función principal del hilo. Se ejecuta hasta que le pidan que termine.
  3. join: que pide que el hilo termine.
  4. run: termina.
  5. __del__: finaliza la clase cerrando todo debidamente.
# -*- coding: utf-8 -*-
import threading
import time

class Hilo(threading.Thread):

 def __init__(self):
   # Inicializar la clase
   print("Starting")
   self.stoprequest = threading.Event()
   super(Hilo, self).__init__()

 def __del__(self):
   # Cerrar las cosas correctamente.
   print("Closed")

 def join(self, timeout=None):
   self.stoprequest.set()
   print("Closing")
   super(Hilo, self).join(timeout)

 def run(self):
   # Definición de la función que se ejecutará al hacer el start mientras no se haya pedido un stop.
   while not self.stoprequest.isSet():
     print("A")
     time.sleep(1)

A=Hilo()
A.start()
for count in range(1,4):
  print("B")
  time.sleep(1)
A.join()

Si ejecutáis este script ($ python script.py ) veréis un output muy chulo, pero si lo hacéis varias veces será aún más divertido. Este es el output que me ha dado a mí en dos ejecuciones consecutivas:

ekaitz@ekaitz:~$ python hola
Starting
AB

A
B
A
B
A
Closing
Closed
ekaitz@ekaitz:~$ python hola
Starting
A
B
B
A
A
B
A
Closing
Closed

¿Qué está pasando aquí? ¿Por qué dos ejecuciones del mismo programa tienen outputs diferentes?

No, no he hecho trampa, este es el output real, podéis probarlo.

Lo que pasa aquí es que, como ya sabéis el hilo principal y el que hemos creado nosotros con la clase se ejecutan al mismo tiempo y es aquí donde ocurre la magia.

El tiempo es una de las pocas cosas en la informática que es aleatoria y es por eso que se utiliza en ciertas ocasiones como semilla para crear números aleatorios (se usan más cosas, como el ruido eléctrico, hablaré de eso también otro día, oh yeah!). Cuando digo que el tiempo es aleatorio de forma genérica me refiero a varios casos concretos, que pueden ser los siguientes:

  1. La hora de ejecución del programa no la podemos controlar.
  2. No podemos saber con exactitud cuanto tarda en ejecutarse un comando (a no ser que sea un comando único a nivel de máquina).
  3. No sabemos cuando se le va a dar tiempo de máquina a un programa y cuando otro.

Seguramente haya más cosas detrás de estas, pero para empezar no están nada mal.

Lo primero es que es interesante tomar la hora del programa para generar números aleatorios, aunque serán poco aleatorios, es un punto a tener en cuenta a la hora de hacer semillas. Las semillas son una forma de empezar a generar números aleatorios, sobre ellas se hacen operaciones matemáticas para construirlos (buscad por ahí, es interesante). ¿Por qué es aleatorio esto? Fácil, porque el usuario es libre de ejecutar el programa cuando quiera, sin avisar siquiera.

Lo segundo se refiere a que cuando se va a ejecutar una línea de código, no sabemos cuanto tiempo va a tardar en hacerse. Esto ocurre porque, aunque para nosotros sean un único comando, a nivel de máquina pueden ser cientos y porque, al mismo tiempo, es posible que la máquina tenga más procesos que irá intercalando. Lo que nos lleva al tercer punto.

Como sabréis (porque lo expliqué un poco aquí), los procesos no se tienen por qué ejecutar en procesadores independientes, normalmente, el sistema operativo va rotando entre ellos para ejecutarlos (nuestros amigos el scheduler y el dispatcher aparecen por aquí) asignando tiempo de ejecución a cada uno. Siendo así, no sabemos cuando se le va a dar tiempo de máquina a un proceso ni cuando a otro. Por eso nuestros programas tienen que tener un tratamiento minucioso de recursos compartidos. Si tenemos varios threads no podrán editar la misma variable a la vez porque podrían corromperla etc. Tendremos que utilizar herramientas especiales para evitarlo, semáforos, mutex y cosas de esas.

Además, para completarlo, el tercer punto y el segundo son un problema común porque, aunque supiéramos cuanto dura un comando (si es un único comando de máquina podríamos saberlo) no podríamos saber cuando se ejecuta (así que no sabríamos cuando termina ni cuando empieza) si tenemos más hilos o procesos en la máquina.

Aunque a nivel bajo, llegando a la máquina (programando microcontroladores en ensamblador por ejemplo), es posible gestionar los dos últimos puntos y jugar con ellos, en un nivel alto (ojo, estoy considerando C nivel alto) no es posible y para muestra lo que hemos visto en el output del script, se consigue una salida aleatoria porque no se sabe cuando se va a ejecutar cada comando. Además, a veces veréis que salen las dos letras seguidas y luego dos saltos de línea consecutivos porque el procesador ha dado tiempo de ejecución a un hilo mientras el otro estaba a medio escribir.

Espero que esto os haya servido de algo. Resumo las ideas importantes y añado un par de links interesantes:

  • No sabemos qué hora es.
  • No sabemos cuanto tiempo tarda un comando.
  • Los hilos se ejecutan a la vez. Pero a nivel de máquina no tiene por qué. Y no sabemos cuando se hace qué.

Y unas referencias duras de las mías:

Pues eso. Paz y amor para todos.

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s