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.
Asyncio
usa designs diferentes :, event loop
corotinas e future
.
- event loop . .
- ( ) – , Python, await event loop. event loop. Tasks, Future.
- Future , . exception.
Com a ajuda,
asyncio
você 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 yield
nos 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
asyncio
e aiohttp
nem 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_url
usando 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.
.