8.1. Concorrência cooperativa¶
O modelo de escalonamento do asyncio é cooperativo, não preemptivo. Essa distinção é o modelo mental mais importante sobre o qual o restante desta seção se baseia, então vale a pena fixá-la antes que qualquer código apareça.
8.1.1. Preemptivo vs cooperativo¶
Um escalonador preemptivo – o tipo que um sistema operacional de desktop usa para manter muitos programas em execução ao mesmo tempo – pode pausar qualquer trecho de código em execução em qualquer momento e alternar para outro. O código em execução não precisa fazer nada de especial; o escalonador o interrompe. Isso torna o escalonamento preemptivo muito flexível (nenhum trecho de código pode privar os demais por ser lento), mas também significa que qualquer variável compartilhada precisa ser defendida com cuidado, porque a troca pode acontecer em qualquer ponto – até mesmo no meio da escrita de um valor, ou no meio da leitura de uma lista.
Um escalonador cooperativo só pode alternar entre trechos de código nos pontos em que o trecho em execução devolve o controle explicitamente. No asyncio, esses pontos são cada await e cada chamada a uma corrotina que cede internamente (mais comumente asyncio.sleep()). Entre dois awaits, a corrotina em execução tem a CPU só para si.
Duas consequências surgem disso:
Uma corrotina que nunca faz await nunca é pausada. Se uma corrotina fica presa em um loop apertado sem nenhum
awaitdentro, ela monopoliza o escalonador e nada mais progride. A solução é fazerawait asyncio.sleep_ms(0)(ou alguma outra chamada de espera) em um ponto sensato do loop.O estado compartilhado é seguro entre awaits. Duas corrotinas não podem se intercalar no meio de uma operação que não tem nenhum
awaitnela. O tipo de corrupção que surge quando a preempção cai no meio de uma atualização de várias etapas – um trecho de código lendo um valor enquanto outro está no meio de sua alteração – simplesmente não pode acontecer aqui. A coordenação entre corrotinas ainda é necessária quando várias delas precisam compartilhar um recurso ao longo de awaits, mas o problema de intercalação no meio de uma linha não se aplica.
8.1.2. As três camadas¶
Todo script asyncio é construído a partir das mesmas três camadas. As próximas duas páginas as abordam em detalhes; estes são os rótulos a se ter em mente ao lê-las.
Corrotinas – funções declaradas com
async def, cada uma uma unidade de trabalho autocontida que faz await onde apropriado. A Visão Geral do Python apresentou as palavras-chaveasync/await; no asyncio, é assim que uma corrotina cede de volta ao escalonador.Tasks – um invólucro que
asyncio.create_task()coloca em volta de uma corrotina para escaloná-la concorrentemente com a atual. A aplicação normalmente cria um punhado de tasks para os trabalhos de longa duração (o loop de snapshot, o cliente de rede, o leitor UART, …).O event loop – o motor por baixo que controla quais corrotinas estão esperando e quais estão prontas para executar, alternando entre as tasks a cada
await. A aplicação não escreve o loop; ela entrega uma corrotina de nível superior aasyncio.run()e o loop conduz tudo a partir daí.
Quando a aplicação é descrita dessa forma – como um pequeno conjunto de corrotinas compostas por um event loop – a concorrência se torna uma propriedade da forma do programa, não algo que a aplicação precisa gerenciar passo a passo.