8.8. Locks#
asyncio.Lock fornece exclusão mútua entre corrotinas – a garantia de que apenas uma corrotina por vez detém o lock, enquanto as outras aguardam até que o detentor o libere.
8.8.1. Quando um lock é necessário#
A página concorrência cooperativa observou que duas corrotinas não podem se intercalar no meio de um trecho de código que não contém nenhum await. O oposto também é verdadeiro: assim que uma corrotina faz await, o loop fica livre para executar outra corrotina. Se duas corrotinas acessam o mesmo recurso entre awaits – um barramento UART, I2C ou SPI – suas operações podem se intercalar de maneiras que corrompem o recurso.
Um lock em torno da seção crítica fecha essa lacuna:
import asyncio
bus_lock = asyncio.Lock()
async def read_register(bus, addr):
async with bus_lock:
bus.write(addr)
return await bus.read(2)
Agora ambas as corrotinas podem chamar read_register simultaneamente; o lock garante que apenas uma delas detenha o barramento por vez, e a outra aguarda a liberação do lock antes de começar.
Locks não são necessários quando a seção crítica não contém nenhum await – a garantia do escalonamento cooperativo já cobre esse caso. Eles só são necessários quando a seção crítica cede o controle ao loop no meio do caminho.
8.8.2. O idioma async with#
O exemplo acima mostra a forma recomendada de usar um lock: dentro de um bloco async with. Na entrada, o bloco faz await em acquire(); na saída (seja porque o bloco retornou normalmente, lançou uma exceção ou foi cancelado) o lock é liberado automaticamente. Não há nenhum caminho de saída do bloco que deixe o lock retido.
Para o caso raro em que o tempo de vida do lock não coincide com um bloco de código, os métodos também estão disponíveis diretamente:
await bus_lock.acquire()
try:
bus.write(addr)
result = await bus.read(2)
finally:
bus_lock.release()
O try/finally é necessário para tornar isso equivalente à versão com async with. A forma async with existe porque este é o formato correto e a linguagem o torna conciso.
8.8.3. Referência de métodos#
acquire()– uma corrotina. Bloqueia até que o lock seja destravado e então o adquire.release()– libera o lock. Se houver corrotinas enfileiradas aguardando emacquire(), a próxima da fila é escalonada para execução e o lock permanece travado; caso contrário, o lock fica destravado.locked()– retornaTruese o lock estiver atualmente retido,Falsecaso contrário. Retorna imediatamente; não bloqueia.
As tarefas em espera são atendidas em ordem FIFO. Não há prioridade, nem reentrância (a mesma tarefa não pode adquirir um lock que já detém) e nenhum timeout na aquisição. Para impor um prazo a uma aquisição de lock, envolva o acquire em asyncio.wait_for()
try:
await asyncio.wait_for(bus_lock.acquire(), timeout=1)
except asyncio.TimeoutError:
# lock busy for >1 s -- bail out
return None
try:
...
finally:
bus_lock.release()