8.1. Concorrência cooperativa¶
O modelo de escalonamento do asyncio é cooperativo, não preemptivo. Esta distinção é o modelo mental mais importante sobre o qual assenta o resto desta secção, pelo que vale a pena fixá-la antes de aparecer qualquer código.
8.1.1. Preemptivo vs. cooperativo¶
Um escalonador preemptivo — como o que um sistema operativo de secretária utiliza para manter vários programas em execução simultânea — pode interromper o código em execução a qualquer momento e alternar para outro. O código em execução não precisa de fazer nada de especial; o escalonador interrompe-o. Isto torna o escalonamento preemptivo muito flexível (nenhum fragmento de código pode privar os outros por ser lento), mas também significa que qualquer variável partilhada tem de ser protegida com cuidado, porque a mudança pode ocorrer em qualquer ponto — mesmo a meio de uma escrita de valor, ou a meio da leitura de uma lista.
Um escalonador cooperativo só pode alternar entre fragmentos de código nos pontos em que o fragmento atualmente em execução cede explicitamente o controlo. No asyncio, esses pontos são cada await e cada chamada a uma corrotina que cede internamente (mais comummente asyncio.sleep()). Entre dois awaits, a corrotina em execução tem a CPU para si.
Duas consequências decorrem disto:
Uma corrotina que nunca aguarda nunca é pausada. Se uma corrotina ficar num ciclo fechado sem nenhum
awaitdentro, monopoliza o escalonador e nada mais progride. A solução é fazerawait asyncio.sleep_ms(0)(ou outra chamada de espera) num ponto adequado do ciclo.O estado partilhado é seguro entre awaits. Duas corrotinas não podem intercalar-se a meio de uma operação que não tenha nenhum
await. O tipo de corrupção que ocorre quando a preempção acontece a meio de uma atualização com vários passos — um fragmento de código a ler um valor enquanto outro está a meio de o alterar — simplesmente não pode acontecer aqui. A coordenação entre corrotinas ainda é necessária quando várias têm de partilhar um recurso através de awaits, mas o problema de intercalação a meio de uma linha não se aplica.
8.1.2. As três camadas¶
Todos os scripts asyncio são construídos a partir das mesmas três camadas. As duas páginas seguintes cobrem-nas em detalhe; estes são os rótulos a ter em mente durante a leitura.
Corrotinas — funções declaradas com
async def, cada uma sendo uma unidade de trabalho autossuficiente que aguarda onde apropriado. A visão geral do Python apresentou as palavras-chaveasync/await; no asyncio, são a forma de uma corrotina ceder o controlo ao escalonador.Tarefas — um invólucro que
asyncio.create_task()coloca à volta de uma corrotina para a escalonar concorrentemente com a atual. A aplicação cria tipicamente um conjunto de tarefas para os trabalhos de longa duração (o ciclo de captura de imagem, o cliente de rede, o leitor UART, …).O ciclo de eventos — o motor subjacente que controla quais corrotinas estão à espera e quais estão prontas a executar, alternando entre tarefas a cada
await. A aplicação não escreve o ciclo; entrega uma corrotina de nível superior aasyncio.run()e o ciclo trata de tudo a partir daí.
Quando a aplicação é descrita desta forma — como um pequeno conjunto de corrotinas compostas por um ciclo de eventos — a concorrência torna-se uma propriedade da estrutura do programa, e não algo que a aplicação tem de gerir passo a passo.