Понимание асинхронного Python

Python

причина

Возникновение асинхронности в основном однопотоковое io ожидание.Так как большинство задач ждут io, то если разрешить работу одному потоку, то все задачи выполняются в конвейерном виде.Если один запрос занимает 1 секунду, а пять запросов - пять секунд , то если можно Пускать одновременно, то скорость можно увеличить в пять раз

Есть два способа, как выполнять пять задач одновременно

  1. Многопоточность
  2. асинхронный

Любой, кто отлаживал слишком много потоков, знает, что поток копирует основной поток с нуля. Открытие нескольких потоков не только затратно, но и дорого для отладки. Асинхронный режим отличается. Вы можете запрограммировать его как один поток. чем многопоточность

Несколько реализаций асинхронного Python

Существует много фреймворков для реализации асинхронности в Python, но основная идея, вероятно, основана на следующих двух способах:

  • twister
  • gevent

Идея твистера в том, чтобы инкапсулировать асинхронные операции и работать через обратные вызовы.scrapyРеализация промежуточного запросаtwisterСпособ

scrapy.Request(url='xxx', callback=func)

инкапсулируется путем прохожденияrequest, когда фреймворк выполнит для нас запрос, он перезвонит через обратный вызов.Если ваш запрос простой, это нормально, вам нужно перезвонить только один раз.Если ваш запрос сложный, то вы будете вводить回调地狱(callback hell)

И вам также нужно написать для обработки исключений, генерируемых различными обратными вызовами, вы можете видетьscrapyРеализация промежуточного программного обеспечения знаетscrapyНасколько громоздкой является обработка исключений. Но наличие середины облегчает нам кодирование модулей, поэтому не будем говорить об этом здесь.

twisterЭтот тип обратного вызова является античеловеческим. Он должен полагаться на ядро, стоящее за ним, для планирования. Без поддержки ядра, стоящего за ним, он вообще не может работать, и поскольку он полагается на обратные вызовы для последующих шагов, наш код должен быть разделены на разные части.часть, если мы не знаем, как ядро ​​​​за функциями обратного вызова или ограничениями, мы не знаем, что эти две функции вообще связаны

Этот метод программирования более благоприятен для разработки модулей, но для нас, знакомых с последовательным программированием, этот метод обратного вызова, очевидно, является кошмаром по сравнению сtwisterЭтот метод обратного вызова,geventОбратные вызовы выполнены в виде зеленых сопрограмм.

PEP-380 определяетyield fromУтверждение, Python3.3 начал использовать, чтобы различать сопрограммы и генераторы, Python3.5 начал использоватьawaitзаменятьyield from, чтобы у сопрограммы был специальный метод для объявления (awaitа такжеasync), который используется для обозначения асинхронных функций

Большая часть причины, по которой сопрограммы могут быть блестящими в асинхронности, заключается в том, что сопрограммы по своей природе асинхронны.Чтобы понять сопрограммы, мы можем сравнить их из простого генератора с обычными функциями.

a = (x for x in range(10))
b = [x for x in range(10))

Давайте посмотрим на такой генератор а. Обычно, когда мы используем этот генератор, мы должны добавитьforцикл, чтобы получить значение внутри, если мы попытаемся использоватьa.send(None), мы обнаружим, что получаем последовательность в b из возвращаемого значения в свою очередь

Именно такая функция отправки и приема позволяет нам реализовать «зеленый» обратный вызов, то есть природа сопрограммы делает более логичной запись асинхронно, и по сравнению сtwisterОбратный вызов, обратный вызов сопрограммы более тщательный, он оборачивает «я» и перезванивает всем.

Узнайте об основах асинхронности

Я вкратце рассказал о природе сопрограмм, а теперь рассказываю об основах асинхронного существования.Самое главное для асинхронного существования — это ожидание.Чтобы понять смысл ожидания и последующую интерпретациюasycioбиблиотека, мы сначала используемselectors(пара Python3selectпакет) для демонстрации

import selectors
sel = selectors.DefaultSelector()

объявить одинselectobject sel, теперь мы хотим вызвать эту основную функцию

sel.select(10)

Это 10 является представителемtimeoutДлина времени, то есть самое долгое время ожидания, через 10 секунд мы обнаружили, что этот результат вернул пустой список, что очевидно, мы не указали, что он ждал

selectorsФункцию этой библиотеки очень легко понять, она аналогична отправке письма: если вы хотите дождаться ответа от кого-то еще, если вы не отправляете свое письмо, значит, вы ждали в почтовом ящике, если только вы не не хотите ждать, иначе вас не примут.Ответьте, так ядро ​​этой библиотеки "отправить письмо" (зарегистрироваться) и дождаться письма (выбрать), а затем выбрать обработку письма самостоятельно

import selectors
import socket

sel = selectors.DefaultSelector()

def accept(sock, mask):
    conn, addr = sock.accept() 
    print('accepted', conn, 'from', addr)

sock = socket.socket()
sock.bind(('localhost', 8000))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)

while True:
    events = sel.select()
    for key, mask in events:
        callback = key.data
        callback(key.fileobj, mask)

Самое главное в этой программе то, чтоsel.register,sel.selectа такжеcallbackТам первая — функция регистрации, вторая — ожидание, а последняя — обратный вызов

вышеtwisterСамый простой обратный вызов, как видите, для того, чтобы получить соединениеsockсоединение, мы должны зарегистрировать процесс ожидания, но это простоsockсоединение, чтобы успешно установитьTCPДля подключения нам все равно придется делать трехэтапное рукопожатие, и нам придется разбираться с ошибками при каждом обратном вызове

И вы можете увидеть функцию обратного вызова и основной драйверselect.select()Степень связанности очень высока, мы должны полностью понимать, как система перезванивает, а обработка одной вещи разбивается на секции обратным вызовом.

Далее мы смотрим наgeventизasyncioвыполнить

async def wget(host):
    connect = asyncio.open_connection(host, 80)
    reader, writer = await connect
    header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host
    writer.write(header.encode('utf-8'))
    await writer.drain()
    while True:
        line = await reader.readline()
        if line == b'\r\n':
            break
    writer.close()

Мы успешно использовали функцию для описания процесса установления соединения и общения, если вы немного понимаетеasyncio, вы обнаружите, что это то же самое, что иtwisterОбратный вызов отличается, используйтеawaitКлючевое слово приостанавливает функцию, затем ожидает обратного вызова, а затем выполняет следующие операции в соответствии с обратным вызовом.Мы успешно написали асинхронно с синхронными операторами, и мы использовали нативную реализацию Python, поэтому, когдаasyncioКак гордился Гвидо (отец Python), когда он вышел, вы можете увидеть цитату ниже.Tulip: Async I/O for Python 3Видео выступления

Анализ асинхронной реализации Python

Ранее мы знали, что основа асинхронности ждет, так как же Гвидо реализует асинхронность с помощью сопрограмм?Далее кратко поговорим об основе этой реализации.

Мы сначала ставим вышеtwisterизменить наgeventСпособ

sel = selectors.DefaultSelector()


@asyncio.coroutine
def get_connection(sock):
    sel.register(sock, selectors.EVENT_READ)
    yield True


async def create_connection():
    sock = socket.socket()
    sock.bind(('localhost', 8000))
    sock.listen(100)
    sock.setblocking(False)
    await get_connection(sock)
    conn, addr = sock.accept()
    print('accepted', conn, 'from', addr)


event = create_connection()
event.send(None)
events = sel.select(100)
for key, mask in events:
    try:
        event.send(None)
    except StopIteration:
        pass

Немного изменим вышесказанноеtwisterфункцию, мы создаемget_connectionфункция положитьsockпривязать к нашемуselвыше, а затем перезвонитеTrue, конечно, этот обратный вызов не обрабатывает исключения или что-то еще, и тогда мы заставим сопрограмму отправить емуNoneпусть это начнется, теперь вы находитесь в другойipythonКлиентское исполнение

import socket
socket.socket()..connect(('localhost', 8000))

Затем вы обнаружите, что информация о подключении клиента распечатывается в основном потоке.

Из этого небольшого примера мы знаем, что проблема, которую необходимо решить при реализации асинхронности, — это общедоступный регистр (который может регистрировать все ожидания ввода-вывода), контейнер (в котором могут храниться все сопрограммы) и ядро, которое всегда может выполнять ожидающие обратные вызовы и обработку. обратные вызовы (несколько сопрограмм)

Глубокое погружение в asyncio, чтобы понять асинхронность Python

Благодаря вышеизложенному мы просто знаем, как использовать сопрограммы сselectСотрудничайте для выполнения асинхронных операций, но то, что мы написали выше, это только самая базовая реализация, давайте углубимсяasyncioИсходный код, чтобы узнать, как упростить асинхронность

Цитировать

Фреймворк асинхронного параллелизма Python

Асинхронное программирование на Python: Asyncio
Tulip: Async I/O for Python 3
【Перевод】Углубленное понимание библиотеки Asyncio в python3.4 и механизма асинхронного ввода-вывода в Node.js.