Uma introdução à programação assíncrona em Python

Olá. Preparou a tradução de um artigo interessante na véspera do início do curso básico "Python Developer" .








Introdução



A programação assíncrona é um tipo de programação paralela na qual uma unidade de trabalho pode ser executada separadamente do encadeamento de execução do aplicativo principal . Quando o trabalho termina, o thread principal é notificado de que o fluxo de trabalho foi concluído ou que ocorreu um erro. Há muitos benefícios nessa abordagem, como desempenho aprimorado do aplicativo e maior velocidade de resposta.







A programação assíncrona recebeu muita atenção nos últimos anos e por boas razões. Embora esse tipo de programação possa ser mais complexo que a execução seqüencial tradicional, é muito mais eficiente.



Por exemplo, em vez de aguardar a conclusão da solicitação HTTP antes de continuar, você pode enviar a solicitação e realizar outro trabalho que esteja aguardando na fila usando corotinas assíncronas em Python.



A assincronia é um dos principais motivos da popularidade do Node.js na implementação de back-end. Muito do código que escrevemos, especialmente em aplicativos pesados ​​de E / S, como sites, depende de recursos externos. Ele pode conter qualquer coisa, de uma chamada de banco de dados remoto a solicitações POST a um serviço REST. Assim que você enviar uma solicitação para um desses recursos, seu código aguardará uma resposta. Com a programação assíncrona, você deixa seu código manipular outras tarefas enquanto aguarda uma resposta dos recursos.



Como o Python consegue fazer várias coisas ao mesmo tempo?







1. Múltiplos processos



A maneira mais óbvia é usar vários processos. No terminal, você pode executar seu script duas, três, quatro, dez vezes e todos os scripts serão executados independentemente e simultaneamente. O sistema operacional cuidará da distribuição dos recursos do processador entre todas as instâncias. Como alternativa, você pode usar a biblioteca de multiprocessamento , que pode gerar vários processos, conforme mostrado no exemplo abaixo.



from multiprocessing import Process

def print_func(continent='Asia'):
    print('The name of continent is : ', continent)

if __name__ == "__main__":  # confirms that the code is under main function
    names = ['America', 'Europe', 'Africa']
    procs = []
    proc = Process(target=print_func)  # instantiating without any argument
    procs.append(proc)
    proc.start()

    # instantiating process with arguments
    for name in names:
        # print(name)
        proc = Process(target=print_func, args=(name,))
        procs.append(proc)
        proc.start()

    # complete the processes
    for proc in procs:
        proc.join()


Resultado:



The name of continent is :  Asia
The name of continent is :  America
The name of continent is :  Europe
The name of continent is :  Africa


2. Vários threads



Outra maneira de executar vários trabalhos em paralelo é usar threads. Um encadeamento é uma fila de execução, muito semelhante a um processo, no entanto, você pode ter vários encadeamentos em um único processo, e todos eles compartilharão recursos. No entanto, será difícil escrever código de fluxo por causa disso. Da mesma forma, o sistema operacional fará todo o trabalho duro de alocar a memória do processador, mas o bloqueio global de intérpretes (GIL) permitirá apenas que um encadeamento Python seja executado em uma unidade de tempo, mesmo se você tiver um código multithread. É assim que o GIL no CPython impede a simultaneidade de vários núcleos. Ou seja, você pode executar à força em apenas um núcleo, mesmo se tiver dois, quatro ou mais.



import threading
 
def print_cube(num):
    """
    function to print cube of given num
    """
    print("Cube: {}".format(num * num * num))
 
def print_square(num):
    """
    function to print square of given num
    """
    print("Square: {}".format(num * num))
 
if __name__ == "__main__":
    # creating thread
    t1 = threading.Thread(target=print_square, args=(10,))
    t2 = threading.Thread(target=print_cube, args=(10,))
 
    # starting thread 1
    t1.start()
    # starting thread 2
    t2.start()
 
    # wait until thread 1 is completely executed
    t1.join()
    # wait until thread 2 is completely executed
    t2.join()
 
    # both threads completely executed
    print("Done!")


Resultado:



Square: 100
Cube: 1000
Done!


3. Corotinas e yield:



Corotinas são uma generalização de sub-rotinas. Eles são usados ​​para multitarefa cooperativa, em que um processo renuncia voluntariamente ao controle ( yield) em alguma frequência ou durante períodos de espera para permitir que vários aplicativos sejam executados ao mesmo tempo. As corotinas são semelhantes aos geradores , mas com métodos adicionais e pequenas alterações na maneira como usamos a declaração de rendimento . Os geradores produzem dados para iteração, enquanto as corotinas também podem consumir dados.



def print_name(prefix):
    print("Searching prefix:{}".format(prefix))
    try : 
        while True:
                # yeild used to create coroutine
                name = (yield)
                if prefix in name:
                    print(name)
    except GeneratorExit:
            print("Closing coroutine!!")
 
corou = print_name("Dear")
corou.__next__()
corou.send("James")
corou.send("Dear James")
corou.close()


Resultado:



Searching prefix:Dear
Dear James
Closing coroutine!!


4. Programação assíncrona



A quarta maneira é a programação assíncrona, na qual o sistema operacional não está envolvido. No lado do sistema operacional, você terá um processo no qual haverá apenas um encadeamento, mas ainda poderá executar várias tarefas ao mesmo tempo. Então, qual é o truque aqui?



Resposta: asyncio



Asyncio- um módulo de programação assíncrono que foi introduzido no Python 3.4. Ele foi projetado para usar corotinas e futuros para facilitar a gravação de código assíncrono e o torna quase tão legível quanto o código síncrono devido à falta de retornos de chamada.



Asynciousa designs diferentes :, event loopcorotinas e future.



  • event loop . .
  • ( ) – , Python, await event loop. event loop. Tasks, Future.
  • Future , . exception.


Com a ajuda, asynciovocê pode estruturar seu código para que as subtarefas sejam definidas como corotinas e permitir que você as agende para serem executadas como desejar, inclusive simultaneamente. As corotinas contêm pontos yieldnos quais definimos possíveis pontos de alternância de contexto. Se houver tarefas na fila de espera, o contexto será alternado; caso contrário, não.



Uma mudança de contexto no asyncioé event loop, que transfere o controlo de fluxo a partir de um co-rotina para outro.



No exemplo a seguir, iniciamos 3 tarefas assíncronas, que solicitam individualmente ao Reddit, extraem e exibem o conteúdo do JSON. Usamos aiohttp - uma biblioteca cliente http que garante que mesmo uma solicitação HTTP seja feita de forma assíncrona.



import signal  
import sys  
import asyncio  
import aiohttp  
import json

loop = asyncio.get_event_loop()  
client = aiohttp.ClientSession(loop=loop)

async def get_json(client, url):  
    async with client.get(url) as response:
        assert response.status == 200
        return await response.read()

async def get_reddit_top(subreddit, client):  
    data1 = await get_json(client, 'https://www.reddit.com/r/' + subreddit + '/top.json?sort=top&t=day&limit=5')

    j = json.loads(data1.decode('utf-8'))
    for i in j['data']['children']:
        score = i['data']['score']
        title = i['data']['title']
        link = i['data']['url']
        print(str(score) + ': ' + title + ' (' + link + ')')

    print('DONE:', subreddit + '\n')

def signal_handler(signal, frame):  
    loop.stop()
    client.close()
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)

asyncio.ensure_future(get_reddit_top('python', client))  
asyncio.ensure_future(get_reddit_top('programming', client))  
asyncio.ensure_future(get_reddit_top('compsci', client))  
loop.run_forever()


Resultado:



50: Undershoot: Parsing theory in 1965 (http://jeffreykegler.github.io/Ocean-of-Awareness-blog/individual/2018/07/knuth_1965_2.html)
12: Question about best-prefix/failure function/primal match table in kmp algorithm (https://www.reddit.com/r/compsci/comments/8xd3m2/question_about_bestprefixfailure_functionprimal/)
1: Question regarding calculating the probability of failure of a RAID system (https://www.reddit.com/r/compsci/comments/8xbkk2/question_regarding_calculating_the_probability_of/)
DONE: compsci

336: /r/thanosdidnothingwrong -- banning people with python (https://clips.twitch.tv/AstutePluckyCocoaLitty)
175: PythonRobotics: Python sample codes for robotics algorithms (https://atsushisakai.github.io/PythonRobotics/)
23: Python and Flask Tutorial in VS Code (https://code.visualstudio.com/docs/python/tutorial-flask)
17: Started a new blog on Celery - what would you like to read about? (https://www.python-celery.com)
14: A Simple Anomaly Detection Algorithm in Python (https://medium.com/@mathmare_/pyng-a-simple-anomaly-detection-algorithm-2f355d7dc054)
DONE: python

1360: git bundle (https://dev.to/gabeguz/git-bundle-2l5o)
1191: Which hashing algorithm is best for uniqueness and speed? Ian Boyd's answer (top voted) is one of the best comments I've seen on Stackexchange. (https://softwareengineering.stackexchange.com/questions/49550/which-hashing-algorithm-is-best-for-uniqueness-and-speed)
430: ARM launches “Facts” campaign against RISC-V (https://riscv-basics.com/)
244: Choice of search engine on Android nuked by “Anonymous Coward” (2009) (https://android.googlesource.com/platform/packages/apps/GlobalSearch/+/592150ac00086400415afe936d96f04d3be3ba0c)
209: Exploiting freely accessible WhatsApp data or “Why does WhatsApp web know my phone’s battery level?” (https://medium.com/@juan_cortes/exploiting-freely-accessible-whatsapp-data-or-why-does-whatsapp-know-my-battery-level-ddac224041b4)
DONE: programming


Usando Redis e Redis Queue RQ



Usar asyncioe aiohttpnem sempre é uma boa ideia, especialmente se você estiver usando versões mais antigas do Python. Além disso, há momentos em que você precisa distribuir tarefas entre diferentes servidores. Nesse caso, o RQ (Redis Queue) pode ser usado. Essa é uma biblioteca Python comum para adicionar trabalhos a uma fila e processá-los por trabalhadores em segundo plano. Para organizar a fila, Redis é usado - um banco de dados de chaves / valores.



No exemplo abaixo, adicionamos uma função simples à fila count_words_at_urlusando Redis.



from mymodule import count_words_at_url
from redis import Redis
from rq import Queue


q = Queue(connection=Redis())
job = q.enqueue(count_words_at_url, 'http://nvie.com')


******mymodule.py******

import requests

def count_words_at_url(url):
    """Just an example function that's called async."""
    resp = requests.get(url)

    print( len(resp.text.split()))
    return( len(resp.text.split()))


Resultado:



15:10:45 RQ worker 'rq:worker:EMPID18030.9865' started, version 0.11.0
15:10:45 *** Listening on default...
15:10:45 Cleaning registries for queue: default
15:10:50 default: mymodule.count_words_at_url('http://nvie.com') (a2b7451e-731f-4f31-9232-2b7e3549051f)
322
15:10:51 default: Job OK (a2b7451e-731f-4f31-9232-2b7e3549051f)
15:10:51 Result is kept for 500 seconds


Conclusão



Como exemplo, faça uma exibição de xadrez, onde um dos melhores jogadores de xadrez concorra com um grande número de pessoas. Temos 24 jogos e 24 pessoas com quem você pode jogar e, se o jogador de xadrez jogar com eles de forma síncrona, levará pelo menos 12 horas (assumindo que o jogo médio faça 30 jogadas, o jogador de xadrez pensará durante 5 segundos e o oponente leva cerca de 55 segundos.) No entanto, no modo assíncrono, o jogador de xadrez poderá fazer um movimento e deixar tempo para o oponente pensar, enquanto passa para o próximo oponente e divide o movimento. Assim, você pode fazer uma jogada em todos os 24 jogos em 2 minutos, e todos eles podem ser ganhos em apenas uma hora.



É isso que está implícito quando as pessoas dizem que a assincronia torna as coisas mais rápidas. Estamos falando sobre essa velocidade. Um bom jogador de xadrez não começa a jogar mais rápido, apenas que o tempo é mais otimizado e não é desperdiçado a espera. É assim que funciona.



Por essa analogia, o jogador de xadrez será um processador, e a idéia principal será manter o processador ocioso o mínimo possível. É sempre ter algo para fazer.



Na prática, assincronia é definida como um estilo de programação paralela, em que algumas tarefas liberam o processador durante períodos de espera, para que outras tarefas possam tirar vantagem disso. O Python possui várias maneiras de obter simultaneidade para atender às suas necessidades, fluxo de código, manipulação de dados, arquitetura e casos de uso, e você pode escolher qualquer uma delas.






.







All Articles