Асинхронный ввод-вывод
Скорость ЦП намного выше, чем скорость ввода-вывода, такого как диск и сеть. В потоке ЦП выполняет код чрезвычайно быстро, однако, как только он сталкивается с операциями ввода-вывода, такими как чтение и запись файлов и отправка сетевых данных, ему необходимо дождаться завершения операций ввода-вывода, прежде чем переходить к следующему шагу. Эта ситуация называется синхронным вводом-выводом.
Во время операции ввода-вывода текущий поток приостанавливается, и другой код, который должен быть выполнен ЦП, не может быть выполнен текущим потоком.
Поскольку операция ввода-вывода блокирует текущий поток, другой код не может быть выполнен, поэтому мы должны использовать многопоточность или многопроцессорность для одновременного выполнения кода для обслуживания нескольких пользователей. Каждому пользователю будет выделен поток. Если поток будет приостановлен из-за ввода-вывода, потоки других пользователей не будут затронуты.
Хотя модель многопоточности и многопроцессорности решает проблему параллелизма, система не может увеличивать число потоков до бесконечности. Поскольку накладные расходы системы на переключение потоков также очень велики, когда количество потоков становится слишком большим, процессорное время тратится на переключение потоков, а время фактического выполнения кода сокращается, что приводит к серьезному снижению производительности.
Поскольку проблема, которую мы хотим решить, заключается в серьезном несоответствии между высокой скоростью выполнения ЦП и черепашьей скоростью устройства ввода-вывода, многопоточность и многопроцессорность являются лишь одним из способов решения этой проблемы.
Другой способ решить проблему ввода-вывода — асинхронный ввод-вывод. Когда коду необходимо выполнить трудоемкую операцию ввода-вывода, он только выдает инструкцию ввода-вывода, не ждет результата ввода-вывода, а затем выполняет другой код. Через некоторое время, когда 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
Обратите внимание, что функция потребителя является генератором после передачи потребителя для производства:
- Сначала вызовите c.send(None), чтобы запустить генератор;
- Затем, как только что-то будет произведено, переключитесь на выполнение потребителя через c.send(n);
- Потребитель получает сообщение через yield, обрабатывает его и отправляет результат обратно через yield;
- product получает результат обработки потребителя и продолжает генерировать следующее сообщение;
- 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, которые могут сделать код сопрограммы более кратким и читабельным.
С новым синтаксисом достаточно простой двухэтапной замены:
- Пучок
@asyncio.coroutine
заменитьasync
; - Пучок
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.
Желаю скорейшей победы над новым коронавирусом, Ухань, давай! Давай Китай!