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
