Profesor Miguel Cantillana
INS240 – Lenguajes de Programación
Ingeniería en Computación e Informática
Semestre 2017-1
Thread con Python
Limitaciones del Global Interpreter Lock
El GIL de CPython evita que múltiples threads ejecuten bytecode simultáneamente
El resultado puede variar según el orden en que lleguen los hilos (condición de carrera).
Se debe controlar el acceso a los datos, utilizando lock, condiciones o semáforos.
Paralelismo
import threading
def worker():
"""funcion que realiza el trabajo en el thread"""
print 'Estoy trabajando LP'
return
threads = []
for i in range(3):
t = threading.Thread(target=worker)
threads.append(t)
t.start()
El modo más sencillo para usar un hilo es instanciar un objeto de la clase Thread con una función objetivo y hacer una llamada a su método start().
import threading
def worker(count):
"""funcion que realiza el trabajo en el thread"""
print "Este es el %s trabajo que hago hoy para Genbeta Dev" % count
return
threads = list()
for i in range(3):
t = threading.Thread(target=worker, args=(i,))
threads.append(t)
t.start()
thread_1.py
A los threads se les puede pasar parámetros que después son usados por la función objetivo. Cualquier tipo de objeto puede ser pasado como parámetro a un thread.
import threading
def contar(num_hilo, **datos):
contador = datos['inicio']
incremento = datos['incremento']
limite = datos['limite']
while contador<=limite:
# print('hilo:', num_hilo, 'contador:', contador)
print 'hilo: %d | contador: %d \n' % (num_hilo,contador)
contador+=incremento
for num_hilo in range(3):
hilo = threading.Thread(target=contar,
args=(num_hilo,),
kwargs={'inicio':0,
'incremento':1,
'limite':3})
hilo.start()
thread_2.py
thread_3.py
import threading
import time
def worker():
print threading.currentThread().getName(), 'Lanzado'
time.sleep(2)
print threading.currentThread().getName(), 'Deteniendo'
def servicio():
print threading.currentThread().getName(), 'Lanzado'
print threading.currentThread().getName(), 'Deteniendo'
if __name__ == '__main__':
t = threading.Thread(target=servicio, name='Servicio')
w = threading.Thread(target=worker, name='Worker')
z = threading.Thread(target=worker)
w.start()
z.start()
t.start()
thread_3.py
import threading
import logging
import time
logging.basicConfig( level=logging.DEBUG,
format='[%(levelname)s] - %(threadName)-10s : %(message)s')
def worker():
logging.debug('Lanzado')
time.sleep(2)
logging.debug('Deteniendo')
if __name__ == '__main__':
w = threading.Thread(target=worker, name='Worker')
w.start()
thread_4.py
El módulo logging soporta la inclusión del nombre del hilo de forma nativa
import threading
import logging
import time
logging.basicConfig( level=logging.DEBUG,
format='[%(levelname)s] - %(threadName)-10s : %(message)s')
def daemon():
logging.debug('Lanzado')
time.sleep(2)
logging.debug('Deteniendo')
d = threading.Thread(target=daemon, name='Daemon')
d.setDaemon(True)
d.start()
thread_5.py
def chequear(nombre):
contador = 0
tam = 0
while contador<100:
contador += 1
if os.path.exists(nombre):
estado = os.stat(nombre)
tam = estado.st_size
logging.debug(
'[%d] %s | %d | tam: %d bytes',
contador,
threading.current_thread().getName(),
contador,
tam
)
time.sleep(0.1)
def escribir(nombre):
'''Escribe en archivo'''
contador = 1
while contador<=10:
with open(nombre, 'a') as archivo:
archivo.write('lorem\n')
logging.debug(
'%s | %d ',
threading.current_thread().getName(),
contador
)
time.sleep(0.3)
contador += 1
if __name__ == '__main__':
nombre = 'archivo.txt'
if os.path.exists(nombre):
os.remove(nombre)
hilo1 = threading.Thread(name='chequear',
target=chequear,
args=(nombre,),
daemon=True)
# hilo1.setDaemon(True)
hilo2 = threading.Thread(name='escribir',
target=escribir,
args=(nombre,))
hilo1.start()
hilo2.start()
thread_6.py
def chequear(nombre):
contador = 0
tam = 0
while contador<100:
contador += 1
if os.path.exists(nombre):
estado = os.stat(nombre)
tam = estado.st_size
logging.debug(
'[%d] %s | %d | tam: %d bytes',
contador,
threading.current_thread().getName(),
contador,
tam
)
time.sleep(0.1)
def escribir(nombre):
'''Escribe en archivo'''
contador = 1
while contador<=10:
with open(nombre, 'a') as archivo:
archivo.write('lorem\n')
logging.debug(
'%s | %d ',
threading.current_thread().getName(),
contador
)
time.sleep(0.3)
contador += 1
if __name__ == '__main__':
nombre = 'archivo.txt'
if os.path.exists(nombre):
os.remove(nombre)
hilo1 = threading.Thread(name='chequear',
target=chequear,
args=(nombre,),
daemon=True)
hilo2 = threading.Thread(name='escribir',
target=escribir,
args=(nombre,))
hilo1.start()
hilo2.start()
hilo1.join()
print(hilo1.isAlive())
thread_7.py
import threading
import logging
logging.basicConfig( level=logging.DEBUG,
format='[%(levelname)s] - %(threadName)-10s : %(message)s')
def contar(numero):
contador = 0
while contador<10:
contador+=1
# print(numero, threading.get_ident(), contador)
logging.debug(
'numero: %d | id: %s | contador: %d',
numero,
str(threading.get_ident()),
contador
)
for numero in range(1, 11):
hilo = threading.Thread(target=contar,
args=(numero,),
daemon=True)
hilo.start()
# Obtiene hilo principal
hilo_ppal = threading.main_thread()
# Recorre hilos activos para controlar estado de su ejecución
for hilo in threading.enumerate():
# Si el hilo es hilo_ppal continua al siguiente hilo activo
if hilo is hilo_ppal:
continue
# Se obtiene información hilo actual y núm. hilos activos
logging.debug(
'name: %s | id: %s | dameon: %d | active_count: %d ',
hilo.getName(),
hilo.ident,
hilo.isDaemon(),
threading.active_count()
)
# El programa esperará a que este hilo finalice:
hilo.join()
thread_8.py
import threading
import logging
logging.basicConfig( level=logging.DEBUG,
format='[%(levelname)s] - %(threadName)-10s : %(message)s')
class MiHilo(threading.Thread):
def run(self):
contador = 1
while contador <= 10:
logging.debug('Ejecutando: %s | contador: %d',
threading.current_thread().getName(),
contador
)
contador+=1
for numero in range(10):
hilo = MiHilo()
hilo.start()
thread_9.py
import threading
class MiHilo(threading.Thread):
def __init__(self, group=None, target=None, name=None,
args=(), kwargs=None, *, daemon=None):
super().__init__(group=group, target=target, name=name,
daemon=daemon)
self.arg1 = args[0]
self.arg2 = args[1]
def run(self):
contador = 1
while contador <= 10:
print('ejecutando...',
'contador', contador,
'argumento1', self.arg1,
'argumento2', self.arg2)
contador+=1
for numero in range(10):
hilo = MiHilo(args=(numero,numero*numero), daemon=False)
hilo.start()
thread_10.py
import threading
import time
import logging
logging.basicConfig( level=logging.DEBUG,
format='[%(levelname)s] - %(threadName)-10s : %(message)s')
def retraso():
logging.debug('worker en ejecución')
return
th1 = threading.Timer(2, retraso)
th1.setName('th1')
th2 = threading.Timer(2, retraso)
th2.setName('th2')
logging.debug('lanzando temporizadores')
th1.start()
th2.start()
logging.debug('esperando antes de cancelar a %s', th2.getName())
time.sleep(1)
logging.debug('cancelando a %s', th2.getName())
th2.cancel()
logging.debug('hecho')
thread_11.py
import threading
total = 0
def acumula5():
global total
contador = 0
hilo_actual = threading.current_thread().getName()
while contador < 20:
print('Esperando para bloquear', hilo_actual)
bloquea.acquire()
try:
contador = contador + 1
total = total + 5
print('Bloqueado por', hilo_actual, contador)
print('Total', total)
finally:
print('Liberado bloqueo por', hilo_actual)
bloquea.release()
bloquea = threading.Lock()
hilo1 = threading.Thread(name='h1', target=acumula5)
hilo2 = threading.Thread(name='h2', target=acumula5)
hilo1.start()
hilo2.start()
thread_14.py
thread_14.py
import threading
def acumula5():
global total
contador = 0
hilo_actual = threading.current_thread().getName()
num_intentos = 0
while contador < 20:
lo_consegui = bloquea.acquire(blocking=False)
try:
if lo_consegui:
contador = contador + 1
total = total + 5
print('Bloqueado por', hilo_actual, contador)
print('Total', total,hilo_actual)
else:
num_intentos+=1
print('Número de intentos de bloqueo',
num_intentos,
'hilo',
hilo_actual,
bloquea.locked())
print(hilo_actual,'Hacer otro trabajo')
finally:
if lo_consegui:
print('Liberado bloqueo por', hilo_actual)
bloquea.release()
total = 0
bloquea = threading.Lock()
hilo1 = threading.Thread(name='h1', target=acumula5)
hilo2 = threading.Thread(name='h2', target=acumula5)
hilo1.start()
hilo2.start()
thread_15.py
La forma más fácil de hacer que un hilo espere a que otro hilo le avise es por medio de Event.
import threading, random
def gen_pares():
num_pares = 0
print('Números:', end=' ')
while num_pares < 25:
numero = random.randint(1, 10)
resto = numero % 2
if resto == 0:
num_pares +=1
print(numero, end=' ')
print()
def contar():
contar = 0
nom_hilo = threading.current_thread().getName()
print(nom_hilo, "en espera")
estado = evento.wait()
while contar < 25:
contar+=1
print(nom_hilo, ':', contar)
evento = threading.Event()
hilo1 = threading.Thread(name='h1', target=contar)
hilo2 = threading.Thread(name='h2', target=contar)
hilo1.start()
hilo2.start()
print('Obteniendo 25 números pares...')
gen_pares()
print('Ya se han obtenido')
evento.set()
thread_16.py
import threading
def avanza(evento):
ciclo = 0
valor = 0
while valor < 20:
estado = evento.wait()
if estado:
ciclo+=1
valor+=1
print('avanza', valor)
if ciclo == 10 and hilo2.isAlive():
evento.clear()
ciclo = 0
print('avanza: ha finalizado')
def retrocede(evento, tiempo):
ciclo = 0
valor = 21
while valor > 1:
estado = evento.wait(tiempo)
if not estado:
ciclo+=1
valor-=1
print('retrocede', valor)
if ciclo == 5 and hilo1.isAlive():
evento.set()
ciclo = 0
print('retrocede: ha finalizado')
evento = threading.Event()
hilo1 = threading.Thread(target=avanza,
args=(evento,),)
hilo2 = threading.Thread(target=retrocede,
args=(evento, 0.5),)
hilo1.start()
hilo2.start()
thread_17.py