Magia Negra das Threads em Python
Certo dia estava eu a pensar sobre a vida o universo e tudo mais e antes que 42 viesse a minha mente foi pedido que eu implementasse um disparador de SMS’s. Para isso eu precisava fazer disparos simultâneos em X canais gsm. Requisito óbvio: preciso de threads. PHP, a linguagem que usamos para quase tudo na aplicação não tem um suporte bom a threads então fui atrás de outra linguagem para o fazer.
Python atravessou meu caminho e nas minhas brincadeiras com a cobra (ui!) eu apanhei, jesus sabe que eu apanhei dessa pseudo-linguagem medonha e caracu (eu xinguei de coisa pior enquanto era humilhado pela até então ignorância na linguagem). Eu apanhei por que o mau costume de só ter usado Java (pausa para uivos e pedras….) para trabalhar com threads me deixou preguiçoso e mau acostumado a uma declaração bem mais simples de sincronismo entre métodos, bastava um synchronized e tudo estava funcionando.
Python ao contrário de Java não é nada thread-safe. Todas as chamadas simples podem ser desastrosas se mexem com recursos compartilhados entre as threads.
Sincronizando
Já que a python não é thread-safe temos que assumir essa responsabilidade no lugar da linguagem e sincronizar as chamadas com Locks, Semáforos e afins. Não é o fim do mundo, mas que é chato, é. Se quizermos por exemplo fazer a inserção de dados no banco de dados e ter o resultado disso logo após cada insert devemos sincronizar os inserts e commits já que o componente MySQLdb não faz esse controle para nós.
class Inserter(object):
def __init__(self, lck,db):
self.lock = lck;
self.db = db
def insert(self, insert):
cursor = self.db.cursor()
try:
self.lock.acquire()
cursor.execute(insert)
cursor.execute("commit")
finally:
self.lock.release()
Queue
Outro componente muito útil quando se utiliza threads são as Filas. Filas são fifos (first-in first-out) que enfileiram ações que você gostaria que fossem executadas em paralelo por threads ou que simplesmente sejam executadas em determinada ordem.
# Importamos o módulo
from Queue import Queue
def run(self):
while True:
try:
self.lock.acquire();
if self.fila.empty():
break
item = self.fila.get()
finally:
self.lock.release()
self.printer.imprime(item)
Uma nota sobre Filas é que não se deve usar um if not queue.empty() e depois tentar dar um queue.get() já que no tempo entre esses dois acessos aos métodos outra thread pode pegar o ultimo elemento. É necessário que você simplesmente pegue o elemento e trate a exceção ou use Lock nessa parte do método.
ps. estranho, mas python implementa LIFO (pilha) no modulo Queue.
Tags: desenvolvimento, how to, python, threads
