Dando continuidade o artigo sobre thread, um recurso muito útil é lock reentrante.
Lock reentrante é uma variação de lock que pode ser realocado múltiplas vezes pelo mesmo thread e não pode ser liberado por outro thread. É muito útil em funções recursivas, mas funciona também para garantir que o lock seja alocado e liberado pelo mesmo thread.
A fábrica (factory) para criar locks reentrantes é
É preciso tomar cuidado para que todos os threads compartilhem o mesmo lock, senão ele se torna inútil:
Lock reentrante é uma variação de lock que pode ser realocado múltiplas vezes pelo mesmo thread e não pode ser liberado por outro thread. É muito útil em funções recursivas, mas funciona também para garantir que o lock seja alocado e liberado pelo mesmo thread.
A fábrica (factory) para criar locks reentrantes é
threading.RLock
.É preciso tomar cuidado para que todos os threads compartilhem o mesmo lock, senão ele se torna inútil:
lock = RLock()
thr1 = Thread(target=func1, args=(lock, ))
thr2 = Thread(target=func2, args=(lock, ))
thr3 = Thread(target=func3, args=(lock, ))
Dentro da função, é preciso alocá-lo (
acquire
) no inicío e liberá-lo (release
) ao final. Por exemplo:
def func1(lock):
lock.acquire()
try:
# executa o procedimento
...
finally:
lock.release()
Protegendo um objeto mutável
Uma utilidade para o lock reentrante é proteger métodos que alterem o conteúdo de um objeto.
Imagine que temos uma classe
Podemos criar um decorador que torna um método thread-safe usando lock reentrante:
Imagine que temos uma classe
Person
com dados, como identity_code
(CPF) que podem sofrer alterações em threads diferentes (sei que não é uma boa abordagem, mas apenas como exemplo).Podemos criar um decorador que torna um método thread-safe usando lock reentrante:
def lock(wrapped):
lock_ = RLock()
@wraps(wrapped)
def wrapper(*args, **kwargs):
with lock_:
return wrapped(*args, **kwargs)
return wrapper
Esse decorador pode ser usado nos setters de cada propriedade:
class Person(object):
...
@property
def identity_code(self):
return self.__identity_code
@identity_code.setter
@lock
def identity_code(self, value):
self.__identity_code = value
...
Na verdade essa abordagem não resolve 100% o problema, mas já reduz muito a ocorrência de bugs.
Protegendo qualquer objeto
Porém a abordagem acima apenas protege parcialmente o objeto e não funciona para classes de terceiros.
Outra abordagem é usar um lock para todo o objeto, tanto leitura quanto gravação, agnóstico a qual objeto está sendo protegido. Assim, não há necessidade de usarmos propriedades.
Vamos criar uma classe para trancar qualquer objeto:
Outra abordagem é usar um lock para todo o objeto, tanto leitura quanto gravação, agnóstico a qual objeto está sendo protegido. Assim, não há necessidade de usarmos propriedades.
Vamos criar uma classe para trancar qualquer objeto:
class ObjectLocker(object):
def __init__(self, obj):
self.__obj = obj
self.__lock = RLock()
def __enter__(self):
self.__lock.acquire()
return self.__obj
def __exit__(self, etype, exc, traceback):
self.__lock.release()
No código a instância dessa classe será passada para os threads, que terá de usar
Ao usar
A passagem será:
with
para acessar o objeto original.Ao usar
with
, o objeto original será trancado para o thread atual e liberado ao final.A passagem será:
locker = ObjectLocker(Person(...))
thr = Thread(target=func, args=(locker, ))
Dentro da(s) função(ões)
func
a instância de Person
deve ser acessa da seguinta forma:
with locker as person:
name = person.name
person.identity_code = data['identity_code']
Espero que os exemplos tenham sido úteis.
[]’sCacilhας, La Batalema