Programação assíncrona em Python: uma breve visão geral

Quando eles falam sobre a execução do programa, “execução assíncrona” significa uma situação em que o programa não espera a conclusão de um determinado processo, mas continua a trabalhar independentemente dele. Um exemplo de programação assíncrona é um utilitário que, trabalhando de forma assíncrona, grava em um arquivo de log. Embora esse utilitário possa falhar (por exemplo, devido à falta de espaço livre em disco), na maioria dos casos ele funcionará corretamente e pode ser usado em vários programas. Eles poderão ligar para ela, passando os dados para a gravação e, depois disso, poderão continuar fazendo suas próprias coisas.



O uso de mecanismos assíncronos ao escrever um determinado programa significa que esse programa será executado mais rapidamente do que sem o uso de tais mecanismos. Ao mesmo tempo, o que está planejado para ser lançado de forma assíncrona, como um utilitário para log, deve ser escrito levando em consideração emergências. Por exemplo, um utilitário para registro, se o espaço em disco acabar, pode simplesmente parar o registro e não "travar" o programa principal com um erro.

A execução assíncrona de código geralmente envolve a operação desse código em um thread separado. Isto é - se estamos falando de um sistema com um processador de núcleo único. Em sistemas com processadores com vários núcleos, esse código pode muito bem ser executado por um processo usando um núcleo separado. Um processador de núcleo único em um determinado momento pode ler e executar apenas uma instrução. É como ler livros. Você não pode ler dois livros ao mesmo tempo.

Se você está lendo um livro e outra pessoa está lhe dando outro livro, você pode pegar este segundo livro e começar a lê-lo. Mas o primeiro terá que ser adiado. A execução de código multithread é organizada com o mesmo princípio. E se várias de suas cópias lessem vários livros ao mesmo tempo, seria semelhante à forma como os sistemas multiprocessadores funcionam.

Se em um processador de núcleo único é muito rápido alternar entre tarefas que requerem poder de computação diferente (por exemplo, entre determinados cálculos e leitura de dados de um disco), você pode sentir que um único núcleo de processador faz várias coisas ao mesmo tempo. Ou, digamos, isso acontece se você tentar abrir vários sites em um navegador ao mesmo tempo. Se o navegador usar um fluxo separado para carregar cada uma das páginas, tudo será feito muito mais rápido do que se essas páginas carregassem uma de cada vez. Carregar uma página não é uma tarefa tão difícil, ele não utiliza os recursos do sistema ao máximo, como resultado, o lançamento simultâneo de várias dessas tarefas é uma ação muito eficaz.

Programação assíncrona em Python


Inicialmente, o Python usou corotinas baseadas em gerador para resolver tarefas de programação assíncronas. Então, no Python 3.4, o módulo asyncio (às vezes, seu nome é escrito como async IO / async IO ), que implementa mecanismos de programação assíncrona. O Python 3.5 introduziu a construção assíncrona / aguardada.

Para fazer desenvolvimento assíncrono em Python, você precisa lidar com alguns conceitos. Estes são a rotina e a tarefa.

Coroutines


Normalmente, a corotina é uma função assíncrona. A corotina também pode ser um objeto retornado de uma função de corotina.

Se, ao declarar uma função, for indicado que é assíncrono, você poderá chamá-la usando a palavra-chave await :

 await say_after(1, 'hello') 

Tal construção significa que o programa será executado até encontrar uma expressão de espera, após o que chamará a função e pausará sua execução até que o trabalho da função chamada seja concluído. Depois disso, outras corotinas também poderão iniciar.

Pausar um programa significa que o controle retorna ao loop de eventos. Ao usar o módulo asyncio , o loop de eventos executa todas as tarefas assíncronas, executa E / S e executa subprocessos. Na maioria dos casos, as tarefas são usadas para executar o corutin.

As tarefas


As tarefas permitem executar corotinas em um loop de eventos. Isso simplifica o controle de execução de várias corotinas. Aqui está um exemplo que usa corotinas e tarefas. Observe que as entidades declaradas usando a construção ass async def são corotinas. Este exemplo é retirado da documentação oficial do Python.

 import asyncio import time async def say_after(delay, what):    await asyncio.sleep(delay)    print(what) async def main():    task1 = asyncio.create_task(        say_after(1, 'hello'))    task2 = asyncio.create_task(        say_after(2, 'world'))    print(f"started at {time.strftime('%X')}")    #     (      #  2 .)    await task1    await task2    print(f"finished at {time.strftime('%X')}") asyncio.run(main()) 

A função say_after() possui o prefixo async ; como resultado, temos uma rotina. Se desviarmos um pouco desse exemplo, podemos dizer que essa função pode ser chamada assim:

     await say_after(1, 'hello')    await say_after(2, 'world') 

Com essa abordagem, no entanto, as corotinas são chamadas seqüencialmente e levam cerca de 3 segundos para serem concluídas. No nosso exemplo, eles são lançados competitivamente. Para cada um deles é usada uma tarefa. Como resultado, o tempo de execução de todo o programa é de aproximadamente 2 segundos. Observe que, para que um programa funcione, não basta declarar a função main() com a async . Em tais situações, você precisa usar o módulo asyncio .

Se você executar o código de exemplo, um texto semelhante ao seguinte será exibido na tela:

 started at 20:19:39 hello world finished at 20:19:41 

Observe que os carimbos de data e hora na primeira e na última linhas diferem em 2 segundos. Se você executar este exemplo com uma chamada seqüencial de corutin, a diferença entre os carimbos de data e hora será de 3 segundos.

Exemplo


Neste exemplo, o número de operações necessárias para calcular a soma de dez elementos de uma sequência de números é determinado. Os cálculos são realizados a partir do final da sequência. Uma função recursiva começa obtendo o número 10 e depois se chama com os números 9 e 8, somando o que será retornado. Isso continua até que os cálculos sejam concluídos. Como resultado, verifica-se, por exemplo, que a soma de uma sequência de números de 1 a 10 é 55. Ao mesmo tempo, nossa função é muito ineficiente, a construção time.sleep(0.1) é usada aqui.

Aqui está o código da função:

 import time def fib(n):    global count    count=count+1    time.sleep(0.1)    if n > 1:        return fib(n-1) + fib(n-2)    return n start=time.time() global count count = 0 result = fib(10) print(result,count) print(time.time()-start) 

O que acontece se você reescrever esse código usando mecanismos assíncronos e aplicar a construção asyncio.gather , responsável por executar duas tarefas e esperar pela conclusão?

 import asyncio,time async def fib(n):    global count    count=count+1    time.sleep(0.1)    event_loop = asyncio.get_event_loop()    if n > 1:        task1 = asyncio.create_task(fib(n-1))        task2 = asyncio.create_task(fib(n-2))        await asyncio.gather(task1,task2)        return task1.result()+task2.result()    return n 

De fato, este exemplo funciona ainda um pouco mais devagar que o anterior, pois tudo é executado em um encadeamento e chama create_task , gather e outros como esse, criando carga adicional no sistema. No entanto, o objetivo deste exemplo é demonstrar a capacidade de competir em várias tarefas e esperar que elas sejam concluídas.

Sumário


Há situações em que o uso de tarefas e corutin é muito útil, por exemplo, se um programa contém uma mistura de entradas e saídas e cálculos, ou se cálculos diferentes são executados no mesmo programa, você pode resolver esses problemas executando o código de uma maneira competitiva em vez de no modo seqüencial. Isso ajuda a reduzir o tempo necessário para o programa executar determinadas ações. No entanto, isso não permite, por exemplo, executar cálculos simultaneamente. O multiprocessamento é usado para organizar esses cálculos. Este é um grande tópico separado.

Caros leitores! Como você escreve código Python assíncrono?


Source: https://habr.com/ru/post/pt475246/


All Articles