RLock.locked: detecte locks reentrantes

Publicado em: 27/10/2026
Tempo de leitura: 5 minutos
Código Python com sincronização por RLock

O método threading.RLock.locked() permite verificar se um bloqueio reentrante está atualmente adquirido. Embora pareça uma operação simples, ele ajuda bastante em diagnósticos, testes, métricas e validações de estado em aplicações concorrentes. Neste guia, você vai entender como o método funciona, quando ele é útil, quais cuidados tomar e como combiná-lo com boas práticas de sincronização.

O que é um RLock?

Um RLock, ou bloqueio reentrante, é uma primitiva de sincronização do módulo threading. Diferentemente de um Lock comum, ele pode ser adquirido várias vezes pela mesma thread sem causar deadlock imediato. Internamente, o objeto mantém o identificador da thread proprietária e um contador de aquisições. Cada chamada a acquire() incrementa esse contador, e cada chamada a release() o reduz. O bloqueio só fica realmente livre quando o contador retorna a zero.

Esse comportamento é útil quando métodos sincronizados chamam outros métodos sincronizados da mesma classe. Sem reentrância, a thread poderia bloquear a si própria. Para uma introdução mais ampla a concorrência, veja também Threading no Python e o que é o GIL.

Como funciona RLock.locked()

O método locked() retorna True quando o objeto está adquirido e False quando está livre. Ele não informa qual thread possui o bloqueio, quantas vezes ele foi adquirido ou por quanto tempo está retido. Portanto, deve ser entendido como uma fotografia momentânea do estado global do objeto.

import threading

lock = threading.RLock()
print(lock.locked())  # False

lock.acquire()
print(lock.locked())  # True

lock.release()
print(lock.locked())  # False

Em código real, prefira usar o gerenciador de contexto:

with lock:
    # seção crítica
    atualizar_cache()

O with garante a liberação mesmo quando ocorre uma exceção. Esse padrão segue as boas práticas apresentadas em como usar with no Python, ainda que o recurso sincronizado aqui seja um lock e não um arquivo.

Quando usar o método

locked() é especialmente útil em testes automatizados, endpoints de diagnóstico, logs de observabilidade e verificações defensivas. Imagine um serviço que possui um cache protegido por RLock. Um painel interno pode exibir se uma atualização está em andamento. Da mesma forma, um teste pode confirmar que determinado callback libera o bloqueio mesmo quando lança uma exceção.

def estado_do_servico():
    return {
        "cache_em_atualizacao": cache_lock.locked(),
        "fila": len(fila),
    }

Apesar disso, o valor não deve ser usado como autorização para entrar em uma seção crítica. Entre a leitura de locked() e uma ação posterior, outra thread pode adquirir ou liberar o bloqueio. Esse é um exemplo clássico de condição de corrida do tipo “verificar e depois agir”.

O erro de verificar antes de adquirir

Evite este padrão:

if not lock.locked():
    lock.acquire()
    try:
        processar()
    finally:
        lock.release()

Mesmo que locked() retorne False, outra thread pode adquirir o objeto antes da chamada a acquire(). O código ainda pode funcionar porque acquire() bloqueará, mas a verificação anterior não oferece garantia alguma. Se a intenção for não esperar, use a própria operação atômica:

if lock.acquire(blocking=False):
    try:
        processar()
    finally:
        lock.release()
else:
    registrar_ocupado()

Essa abordagem combina teste e aquisição em uma única operação segura.

Reentrância e contador interno

Uma diferença importante é que locked() continua retornando True enquanto o contador interno for maior que zero. Se a mesma thread adquirir o RLock duas vezes, será necessário liberar duas vezes.

lock.acquire()
lock.acquire()
print(lock.locked())  # True
lock.release()
print(lock.locked())  # ainda True
lock.release()
print(lock.locked())  # False

Esse detalhe é essencial ao depurar travamentos. Um código pode aparentemente chamar release() e ainda manter o bloqueio, porque houve mais aquisições do que liberações. Para investigar esse tipo de problema, recursos como faulthandler e pdb também podem ajudar.

Exemplo em uma classe thread-safe

import threading

class ContadorSeguro:
    def __init__(self):
        self._valor = 0
        self._lock = threading.RLock()

    def incrementar(self):
        with self._lock:
            self._valor += 1
            self._registrar()

    def _registrar(self):
        with self._lock:
            print(f"valor={self._valor}")

    def ocupado(self):
        return self._lock.locked()

O método incrementar() chama _registrar(), e ambos adquirem o mesmo bloqueio. Isso funciona porque o lock é reentrante. Com um Lock simples, a mesma thread poderia ficar parada aguardando um recurso que ela própria já possui.

Testando a liberação após exceções

def executar_com_falha(lock):
    with lock:
        raise RuntimeError("falha simulada")

lock = threading.RLock()
try:
    executar_com_falha(lock)
except RuntimeError:
    pass

assert lock.locked() is False

Esse teste verifica uma propriedade importante: o gerenciador de contexto libera corretamente o recurso. Em suítes maiores, combine essa abordagem com práticas descritas em testes unitários no Python.

Observabilidade sem decisões perigosas

O método funciona bem para métricas, mas os dados devem ser interpretados como aproximados. Em sistemas muito concorrentes, o estado pode mudar logo após a leitura. Uma métrica como “lock ocupado” é útil para detectar contenção frequente, porém não substitui medições de duração, tamanho de fila ou tempo médio de espera.

Para observabilidade mais robusta, registre o instante antes de chamar acquire(), calcule o tempo de espera depois da aquisição e meça quanto tempo a seção crítica permanece ativa. Esses valores ajudam a localizar gargalos reais.

Diferença entre Lock e RLock

Use Lock quando não houver necessidade de reentrância. Ele é conceitualmente mais simples e reduz o risco de esconder uma arquitetura confusa. Use RLock quando uma mesma thread precisar atravessar camadas sincronizadas que reutilizam o mesmo recurso. O método locked() é útil nos dois contextos, mas a interpretação do RLock exige lembrar do contador de aquisições.

Cuidados importantes

Não use locked() para decidir se é seguro acessar dados compartilhados. Não tente descobrir a thread proprietária por meios privados. Não dependa de atributos internos do CPython, pois eles podem mudar. Evite liberar o bloqueio manualmente em muitos pontos diferentes; prefira concentrar aquisição e liberação em blocos with. Por fim, documente claramente quais atributos são protegidos por cada lock.

Compatibilidade e documentação

Antes de usar o método em bibliotecas distribuídas para várias versões do Python, confira a versão mínima suportada e execute testes na sua matriz de ambientes. A referência oficial de threading descreve o comportamento das primitivas de sincronização. Também vale consultar o What’s New do Python 3.14 para mudanças recentes.

Conclusão

threading.RLock.locked() é uma ferramenta pequena, mas útil para diagnóstico, testes e observabilidade. Seu retorno representa apenas o estado naquele instante e não deve ser usado para implementar uma sequência “verificar e agir”. Para controlar concorrência com segurança, use acquire(), preferencialmente por meio de with, e trate locked() como informação auxiliar. Com essa distinção, você consegue depurar locks reentrantes, validar liberações e construir serviços concorrentes mais previsíveis.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Código Python exibindo avisos e diagnóstico para desenvolvedores
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    skip_file_prefixes: warnings no código correto

    Aprenda a usar skip_file_prefixes no módulo warnings para apontar avisos ao código correto em bibliotecas Python.

    Ler mais

    Tempo de leitura: 5 minutos
    26/10/2026
    Pasta de arquivos protegidos representando extração TAR segura no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    Extração TAR segura com tarfile no Python

    Aprenda a extrair arquivos TAR com segurança no Python usando filtros, limites e validação contra path traversal.

    Ler mais

    Tempo de leitura: 4 minutos
    26/10/2026
    Dois monitores executando código em paralelo
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    concurrent.interpreters: paralelismo com isolamento

    Aprenda concurrent.interpreters no Python para executar tarefas isoladas, comparar com threads e processos e explorar paralelismo real.

    Ler mais

    Tempo de leitura: 7 minutos
    25/10/2026
    Código Python com anotações de tipo em um notebook
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: leia anotações com segurança

    Aprenda annotationlib no Python para ler anotações como valores, strings ou referências futuras com mais segurança.

    Ler mais

    Tempo de leitura: 6 minutos
    25/10/2026
    Ícone de compressão de arquivos ZIP
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ZipInfo.compress_level: controle a compressão ZIP

    Aprenda ZipInfo.compress_level no Python para definir níveis de compressão por arquivo e equilibrar tamanho, CPU e compatibilidade.

    Ler mais

    Tempo de leitura: 5 minutos
    24/10/2026
    Código binário representando bytearray.resize no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    bytearray.resize: redimensione buffers com segurança

    Aprenda bytearray.resize no Python para expandir e reduzir buffers com segurança, limites e compatibilidade.

    Ler mais

    Tempo de leitura: 5 minutos
    24/10/2026