Асинхронный ввод-вывод Python

Python

Асинхронный ввод-вывод

Скорость ЦП намного выше, чем скорость ввода-вывода, такого как диск и сеть. В потоке ЦП выполняет код чрезвычайно быстро, однако, как только он сталкивается с операциями ввода-вывода, такими как чтение и запись файлов и отправка сетевых данных, ему необходимо дождаться завершения операций ввода-вывода, прежде чем переходить к следующему шагу. Эта ситуация называется синхронным вводом-выводом.
Во время операции ввода-вывода текущий поток приостанавливается, и другой код, который должен быть выполнен ЦП, не может быть выполнен текущим потоком.
Поскольку операция ввода-вывода блокирует текущий поток, другой код не может быть выполнен, поэтому мы должны использовать многопоточность или многопроцессорность для одновременного выполнения кода для обслуживания нескольких пользователей. Каждому пользователю будет выделен поток. Если поток будет приостановлен из-за ввода-вывода, потоки других пользователей не будут затронуты.
Хотя модель многопоточности и многопроцессорности решает проблему параллелизма, система не может увеличивать число потоков до бесконечности. Поскольку накладные расходы системы на переключение потоков также очень велики, когда количество потоков становится слишком большим, процессорное время тратится на переключение потоков, а время фактического выполнения кода сокращается, что приводит к серьезному снижению производительности.
Поскольку проблема, которую мы хотим решить, заключается в серьезном несоответствии между высокой скоростью выполнения ЦП и черепашьей скоростью устройства ввода-вывода, многопоточность и многопроцессорность являются лишь одним из способов решения этой проблемы.
Другой способ решить проблему ввода-вывода — асинхронный ввод-вывод. Когда коду необходимо выполнить трудоемкую операцию ввода-вывода, он только выдает инструкцию ввода-вывода, не ждет результата ввода-вывода, а затем выполняет другой код. Через некоторое время, когда IO возвращает результат, он уведомляет ЦП о необходимости его обработки.
Вполне возможно, что если код, написанный в обычном порядке, не может на самом деле завершить асинхронный ввод-вывод, модель асинхронного ввода-вывода требует цикла сообщений, в котором основной поток непрерывно повторяет процесс «чтение сообщений — обработка сообщений».
В период от «отправки запроса ввода-вывода» до получения «завершения ввода-вывода» в синхронной модели ввода-вывода основной поток может быть только приостановлен, но в асинхронной модели ввода-вывода основной поток не отдыхает, а продолжается в цикле обработки сообщений. Обработайте другие сообщения. Таким образом, в модели асинхронного ввода-вывода один поток может обрабатывать несколько запросов ввода-вывода одновременно, и нет операции переключения потоков. Для большинства приложений с интенсивным вводом-выводом использование асинхронного ввода-вывода значительно улучшит возможности многозадачности системы.

сопрограмма

Корутины, также известные как микропотоки, волокна. Английское имя Корутин.
Во всех языках подпрограммы или функции вызываются иерархически, например, A вызывает B, B вызывает C во время выполнения, C возвращает после выполнения, B возвращает после выполнения и, наконец, A завершает выполнение.
Поэтому вызов подпрограммы реализуется через стек, а поток выполняет подпрограмму.
Вызов подпрограммы всегда представляет собой один вход, один возврат, а последовательность вызова однозначна. Вызовы корутин отличаются от подпрограмм.
Сопрограмма выглядит как подпрограмма, но во время выполнения ее можно прервать внутри подпрограммы, а затем повернуть для выполнения других подпрограмм, а затем вернуться для продолжения выполнения в соответствующее время.
Обратите внимание, что прерывание подпрограммы для выполнения других подпрограмм не является вызовом функции, что несколько похоже на прерывания ЦП. Например, подпрограммы A и B, но B не вызываются в A, поэтому вызов сопрограммы сложнее понять, чем вызов функции.
Кажется, что выполнение A и B немного похоже на многопоточность, но особенность сопрограммы в том, что она выполняется одним потоком, в чем преимущество сопрограммы по сравнению с многопоточностью?
Самым большим преимуществом являются сопрограммыОчень высокая эффективность исполнения. Поскольку переключение подпрограмм не является переключением потоков, а управляется самой программой, нет накладных расходов на переключение потоков.По сравнению с многопоточностью, чем больше потоков, тем очевиднее преимущество сопрограмм в производительности.
Второе преимущество в том, чтоМеханизм блокировки, не требующий многопоточности, потому что поток только один, и нет конфликта записи переменных одновременно, разделяемые ресурсы не блокируются в сопрограмме, и нужно судить только о статусе, поэтому эффективность выполнения намного выше, чем у несколько потоков.
Поскольку сопрограмма выполняется потоком, как использовать многоядерный процессор? Самый простой метод — мультипроцесс + сопрограмма, который не только в полной мере использует многоядерность, но и полностью раскрывает высокую эффективность сопрограммы и может обеспечить чрезвычайно высокую производительность.
Поддержка Python для сопрограмм реализована через генераторы.
После того, как производитель создает сообщение, он напрямую переходит к потребителю через yield, чтобы начать выполнение.После того, как потребитель заканчивает выполнение, он переключается обратно на производителя, чтобы продолжить производство, что чрезвычайно эффективно:

def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('[CONSUMER] Consuming %s...' % n)
        r = '200 OK'

def produce(c):
    c.send(None)
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER] Producing %s...' % n)
        r = c.send(n)
        print('[PRODUCER] Consumer return: %s' % r)
    c.close()

c = consumer()
produce(c)

Результаты:

[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK

Обратите внимание, что функция потребителя является генератором после передачи потребителя для производства:

  1. Сначала вызовите c.send(None), чтобы запустить генератор;
  2. Затем, как только что-то будет произведено, переключитесь на выполнение потребителя через c.send(n);
  3. Потребитель получает сообщение через yield, обрабатывает его и отправляет результат обратно через yield;
  4. product получает результат обработки потребителя и продолжает генерировать следующее сообщение;
  5. Produce решает не производить и закрывает потребителя через c.close(), и весь процесс завершается.

Весь процесс не блокируется и выполняется одним потоком.Производитель и потребитель взаимодействуют для выполнения задач, поэтому он называется «сопрограммой», а не упреждающей многозадачностью потоков.

asyncio

Реализация асинхронного ввода-вывода на Python очень проста,asyncioЭто стандартная библиотека, представленная в Python 3.4, со встроенной прямой поддержкой асинхронного ввода-вывода.
asyncioМодель программирования представляет собой цикл сообщений. мы начинаем сasyncioПолучить один прямо из модуляEventLoop, а затем бросьте сопрограмму, которую необходимо выполнить, вEventLoopВ исполнении реализован асинхронный ввод-вывод.
Используйте асинхронное сетевое соединение asyncio, чтобы получить код домашней страницы sina, sohu и 163 следующим образом:

import asyncio

@asyncio.coroutine
def wget(host):
    print('wget %s...' % host)
    connect = asyncio.open_connection(host, 80)
    reader, writer = yield from connect
    header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host
    writer.write(header.encode('utf-8'))
    yield from writer.drain()
    while True:
        line = yield from reader.readline()
        if line == b'\r\n':
            break
        print('%s header > %s' % (host, line.decode('utf-8').rstrip()))
    # Ignore the body, close the socket
    writer.close()

loop = asyncio.get_event_loop()
tasks = [wget(host) for host in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

Результат выполнения следующий:

wget www.sohu.com...
wget www.sina.com.cn...
wget www.163.com...
(等待一段时间)
(打印出sohu的header)
www.sohu.com header > HTTP/1.1 200 OK
www.sohu.com header > Content-Type: text/html
...
(打印出sina的header)
www.sina.com.cn header > HTTP/1.1 200 OK
www.sina.com.cn header > Date: Wed, 20 May 2015 04:56:33 GMT
...
(打印出163的header)
www.163.com header > HTTP/1.0 302 Moved Temporarily
www.163.com header > Server: Cdn Cache Server V2.0
...

@asyncio.coroutineОтметьте генератор как тип сопрограммы, затем мы поместим этоcoroutineбросать вEventLoopв исполнении.
yield fromСинтаксис позволяет нам легко вызвать другой генератор. Таким образом, поток не ждет операции ввода-вывода, а напрямую прерывает и выполняет следующий цикл обработки сообщений. когдаyield fromПри возврате поток можетyield fromПолучите возвращаемое значение, а затем выполните следующую строку операторов.
В этот период основной поток не ждет, а выполняетEventLoopдругой исполняемый файлcoroutine, поэтому мы инкапсулируем три с помощью TaskcoroutineОн может выполняться одновременно одним и тем же потоком.

async/await

Чтобы упростить и лучше идентифицировать асинхронный ввод-вывод, начиная с Python 3.5 были введены новые синтаксисы async и await, которые могут сделать код сопрограммы более кратким и читабельным.
С новым синтаксисом достаточно простой двухэтапной замены:

  1. Пучок@asyncio.coroutineзаменитьasync;
  2. Пучокyield fromзаменитьawait.

Перепишите код из предыдущего раздела с новым синтаксисом следующим образом:

import asyncio

async def wget(host):
    print('wget %s...' % 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
        print('%s header > %s' % (host, line.decode('utf-8').rstrip()))
    # Ignore the body, close the socket
    writer.close()

loop = asyncio.get_event_loop()
tasks = [wget(host) for host in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

Остальной код остается прежним.

aiohttp

asyncioРеализованы такие протоколы, как TCP, UDP, SSL и др.aiohttpосновывается наasyncioРеализован HTTP-фреймворк.
Установите aiohttp:

pip install aiohttp

Затем напишите HTTP-сервер, который обрабатывает следующие URL-адреса соответственно:

  • /- Возвращение домойb'<h1>Index</h1>';
  • /hello/{name}- Возврат текста на основе параметров URLhello, %s!.

код показывает, как показано ниже:

import asyncio

from aiohttp import web

async def index(request):
    await asyncio.sleep(0.5)
    return web.Response(body=b'<h1>Index</h1>')

async def hello(request):
    await asyncio.sleep(0.5)
    text = '<h1>hello, %s!</h1>' % request.match_info['name']
    return web.Response(body=text.encode('utf-8'))

async def init(loop):
    app = web.Application(loop=loop)
    app.router.add_route('GET', '/', index)
    app.router.add_route('GET', '/hello/{name}', hello)
    srv = await loop.create_server(app.make_handler(), '127.0.0.1', 8000)
    print('Server started at http://127.0.0.1:8000...')
    return srv

loop = asyncio.get_event_loop()
loop.run_until_complete(init(loop))
loop.run_forever()

Уведомлениеaiohttpфункция инициализацииinit()такжеcoroutine,loop.create_server()затем используйтеasyncioСоздайте службу TCP.
Желаю скорейшей победы над новым коронавирусом, Ухань, давай! Давай Китай!