Из-за наличия GIL многопоточная производительность Python даже хуже, чем однопоточная.
GIL: Глобальная блокировка интерпретатора (англ. Global Interpreter Lock, сокращенно GIL) — это механизм, используемый интерпретаторами языков компьютерного программирования для синхронизации потоков, чтобы в каждый момент времени выполнялся только один поток. [1] Даже на многоядерных процессорах интерпретаторы, использующие GIL, позволяют одновременно выполнять только один поток.
Итак, есть такое понятие, как сопрограмма (Coroutine).
Coroutine: Coroutine, также известная как микронить, волокно, английское название Coroutine. Роль сопрограммы заключается в том, что при выполнении функции A она может быть прервана в любой момент для выполнения функции B, а затем прервана для продолжения выполнения функции A (которую можно свободно переключать). Но этот процесс не является вызовом функции (нет оператора вызова), весь процесс вроде бы многопоточный, но сопрограмма выполняется только одним потоком.
Так как программа активно управляет переключением, отсутствуют накладные расходы на переключение потоков, поэтому эффективность выполнения чрезвычайно высока. Он очень подходит для задач с интенсивным вводом-выводом, если он интенсивно использует процессор, рекомендуется метод multi-process + coroutine.
До Python 3.4 не было официальной поддержки сопрограмм, и были реализации некоторых сторонних библиотек, таких как gevent и Tornado. После версии 3.4 была встроена стандартная библиотека asyncio и официальная реализация функции сопрограммы.
Поддержка Python для сопрограмм реализована через Generator, а сопрограммы — это генераторы, которые следуют определенным правилам. Итак, прежде чем мы узнаем о сопрограммах, нам нужно узнать о генераторах.
Генератор
В основном мы обсуждаем здесьyield
иyield from
Эти два выражения, эти два выражения тесно связаны с реализацией сопрограммы.
- Представлено в Python 2.5
yield
выражение см.PEP342 - Добавлено в Python 3.3
yield from
синтаксис см.PEP380,
метод содержитyield
После выражения Python будет рассматривать его как объект генератора, а не как обычный метод.
yield
использование выражений
Давайте сначала посмотрим на конкретное использование этого выражения:
def test():
print("generator start")
n = 1
while True:
yield_expression_value = yield n
print("yield_expression_value = %d" % yield_expression_value)
n += 1
# ①创建generator对象
generator = test()
print(type(generator))
print("\n---------------\n")
# ②启动generator
next_result = generator.__next__()
print("next_result = %d" % next_result)
print("\n---------------\n")
# ③发送值给yield表达式
send_result = generator.send(666)
print("send_result = %d" % send_result)
Результаты:
<class 'generator'>
---------------
generator start
next_result = 1
---------------
yield_expression_value = 666
send_result = 2
Описание метода:
-
__next__()
Метод: функция запускает или возобновляет выполнение генератора, что эквивалентно send(None) -
send(value)
Метод: роль заключается в отправке значения в выражение yield. Для запуска генератора нужно вызвать send(None)
Описание результата выполнения:
-
①Создайте объект-генератор: функция, содержащая выражение yield, больше не будет функцией, а объект-генератор будет возвращен после вызова.
-
②Запустите генератор: перед использованием генератора необходимо позвонить
__next__
илиsend(None)
, иначе будет сообщено об ошибке. После запуска генератора код будет выполняться до тех пор, покаyield
Место, где он появляется, то есть исполнение доyield n
, затем передать n вgenerator.__next__()
Возвращаемое значение этой строки. (Обратите внимание, что генератор выполняется доyield n
будет останавливаться здесь до следующего запуска генератора) -
③ Отправить значение в выражение yield: вызовите метод отправки, чтобы отправить значение в выражение yield и возобновить выполнение генератора. Генератор продолжает работу с того места, где он остановился, и сталкивается со следующим
yield
, генератор снова приостанавливается и переключается на основную функцию для вывода send_result.
Ключом к пониманию этой демонстрации является то, что генератор запускает или возобновляет выполнение один раз, что будетyield
Приостановлено в. Шаг 2 выше выполняется толькоyield n
, и оператор присваивания не выполняется. На шаге 3 генератор возобновляет выполнение, прежде чем датьyield_expression_value
Назначение.
Модель производителя и потребителя
В приведенном выше примере прерывание кода --> переключение выполнения отражает некоторые характеристики сопрограмм.
Возьмем другой пример производителя и потребителя. Этот пример взят изУчебное пособие по Python от Ляо Сюэфэна:
Традиционная модель производитель-потребитель заключается в том, что поток пишет сообщения, поток принимает сообщения и управляет очередями и ожиданием с помощью механизма блокировки, но если вы не будете осторожны, могут возникнуть взаимоблокировки.
Вместо этого используйте сопрограмму.После того, как производитель создает сообщение, он напрямую передает
yield
Перейти к потребителю, чтобы начать выполнение, и после того, как потребитель завершит выполнение, переключиться обратно на производителя, чтобы продолжить производство, что чрезвычайно эффективно.
def consumer():
print("[CONSUMER] start")
r = 'start'
while True:
n = yield r
if not n:
print("n is empty")
continue
print("[CONSUMER] Consumer is consuming %s" % n)
r = "200 ok"
def producer(c):
# 启动generator
start_value = c.send(None)
print(start_value)
n = 0
while n < 3:
n += 1
print("[PRODUCER] Producer is producing %d" % n)
r = c.send(n)
print('[PRODUCER] Consumer return: %s' % r)
# 关闭generator
c.close()
# 创建生成器
c = consumer()
# 传入generator
producer(c)
Результаты:
[CONSUMER] start
start
[PRODUCER] producer is producing 1
[CONSUMER] consumer is consuming 1
[PRODUCER] Consumer return: 200 ok
[PRODUCER] producer is producing 2
[CONSUMER] consumer is consuming 2
[PRODUCER] Consumer return: 200 ok
[PRODUCER] producer is producing 3
[CONSUMER] consumer is consuming 3
[PRODUCER] Consumer return: 200 ok
уведомление
consumer
функция представляет собойgenerator
, положитьconsumer
входящийproduce
Задний:
- первый звонок
c.send(None)
запустить генератор;
- Затем, как только что-то будет произведено, передайте
c.send(n)
Переключиться на потребительское исполнение;
consumer
пройти черезyield
Получите сообщение, обработайте его и передайте сноваyield
вернуть результат;
produce
получитьconsumer
Результат обработки, далее выдать следующее сообщение;
produce
Решил не производить, черезc.close()
закрытиеconsumer
, весь процесс заканчивается.
Весь процесс не блокируется и выполняется одним потоком,
produce
иconsumer
Задача выполняется совместно, поэтому она называется «сопрограммой», а не упреждающей многозадачностью потоков.
yield from
выражение
Новое в Python 3.3yield from
грамматика, новая грамматика используется для делегирования операций одной части генератора другому генератору. Кроме того, подгенераторам (то есть «параметрам» после yield from) разрешено возвращать значение, которое может использоваться путем делегирования генераторов (то есть генераторов, которые включают yield from). А при делегировании генераторов можно оптимизировать подгенераторы.
Сначала рассмотрим самые простые приложения, такие как:
# 子生成器
def test(n):
i = 0
while i < n:
yield i
i += 1
# 委派生成器
def test_yield_from(n):
print("test_yield_from start")
yield from test(n)
print("test_yield_from end")
for i in test_yield_from(3):
print(i)
вывод:
test_yield_from start
0
1
2
test_yield_from end
Здесь мы просто добавляем в генератор немного печати, если это формальный код, то можно добавить нормальную логику выполнения.
если вышеtest_yield_from
В функции дваyield from
оператор, будет выполняться последовательно. Например, приведенный вышеtest_yield_from
Функция переписывается следующим образом:
def test_yield_from(n):
print("test_yield_from start")
yield from test(n)
print("test_yield_from doing")
yield from test(n)
print("test_yield_from end")
выведет:
test_yield_from start
0
1
2
test_yield_from doing
0
1
2
test_yield_from end
это здесь,yield from
Эффект эквивалентен сокращенной форме следующего написания
for item in test(n):
yield item
выглядит такyield from
Это не имело большого значения, на самом деле это помогло нам с исключениями и тому подобными вещами. Подробнее см. этот вопрос в stackoverflow:Каковы основные области применения нового синтаксиса «выход из» в Python 3.3 на практике?
Корутина
- Начиная с Python 3.4, были добавлены API, связанные с асинхронностью, и используется синтаксис
@asyncio.coroutine
иyield from
Реализовать сопрограммы - Представлено в Python 3.5
async
/await
синтаксис см.PEP492
Давайте сначала посмотрим на реализацию Python 3.4.
@asyncio.coroutine
В Python 3.4 используйте@asyncio.coroutine
Декорированные функции называются сопрограммами. Однако на грамматическом уровне это строго не ограничено.
Для тех, кто не знает о декораторах, вы можете прочитать мой последний блог--«Понимание декораторов Python»
Для сопрограмм, изначально поддерживаемых Python, Python делает некоторые различия между сопрограммами и генераторами, чтобы облегчить устранение неоднозначности этих двух разных, но связанных концепций:
- отмечен
@asyncio.coroutine
Функция декоратора называется функцией сопрограммы,iscoroutinefunction()
метод возвращает Истина - Объект, возвращаемый вызовом функции сопрограммы, называется объектом сопрограммы.
iscoroutine()
функция возвращает Истина
Возьмите каштан, мы даем вышеyield from
добавить в демо@asyncio.coroutine
:
import asyncio
...
@asyncio.coroutine
def test_yield_from(n):
...
# 是否是协程函数
print(asyncio.iscoroutinefunction(test_yield_from))
# 是否是协程对象
print(asyncio.iscoroutine(test_yield_from(3)))
Нет никаких сомнений в том, что вывод True.
можно посмотреть@asyncio.coroutine
Чтобы увидеть, что он сделал, я упрощу его исходный код, который примерно выглядит следующим образом:
import functools
import types
import inspect
def coroutine(func):
# 判断是否是生成器
if inspect.isgeneratorfunction(func):
coro = func
else:
# 将普通函数变成generator
@functools.wraps(func)
def coro(*args, **kw):
res = func(*args, **kw)
res = yield from res
return res
# 将generator转换成coroutine
wrapper = types.coroutine(coro)
# For iscoroutinefunction().
wrapper._is_coroutine = True
return wrapper
Пометка этого декоратора на генераторе превращает его в сопрограмму.
Тогда давайте на самом деле использовать@asyncio.coroutine
иyield from
:
import asyncio
@asyncio.coroutine
def compute(x, y):
print("Compute %s + %s ..." % (x, y))
yield from asyncio.sleep(1.0)
return x + y
@asyncio.coroutine
def print_sum(x, y):
result = yield from compute(x, y)
print("%s + %s = %s" % (x, y, result))
loop = asyncio.get_event_loop()
print("start")
# 中断调用,直到协程执行结束
loop.run_until_complete(print_sum(1, 2))
print("end")
loop.close()
Результаты:
start
Compute 1 + 2 ...
1 + 2 = 3
end
print_sum
Подпрограмма вызывается в этой сопрограммеcompute
, он будет ждатьcompute
Результат возвращается после завершения выполнения.
Процесс вызова этой демо-точки выглядит следующим образом:
EventLoop поместитprint_sum
Инкапсулирован как объект Task
Блок-схема показывает поток управления этой демонстрации, но не показывает все его детали. Например, единица «паузы» фактически создает будущий объект, а затем передаетBaseEventLoop.call_later()
Разбудить эту задачу через 1 с.
В частности,@asyncio.coroutine
Будет удалено в Python 3.10.
async
/await
Python3.5 начал внедрятьasync
/await
грамматика(PEP 492), чтобы упростить использование сопрограмм и сделать их более понятными.
async
/await
на самом деле просто@asyncio.coroutine
иyield from
синтаксический сахар для:
- Пучок
@asyncio.coroutine
заменитьasync
- Пучок
yield from
заменитьawait
Вот и все.
Например, приведенный выше пример:
import asyncio
async def compute(x, y):
print("Compute %s + %s ..." % (x, y))
await asyncio.sleep(1.0)
return x + y
async def print_sum(x, y):
result = await compute(x, y)
print("%s + %s = %s" % (x, y, result))
loop = asyncio.get_event_loop()
print("start")
loop.run_until_complete(print_sum(1, 2))
print("end")
loop.close()
Давайте посмотрим на другой пример Future в asyncio:
import asyncio
future = asyncio.Future()
async def coro1():
print("wait 1 second")
await asyncio.sleep(1)
print("set_result")
future.set_result('data')
async def coro2():
result = await future
print(result)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait([
coro1()
coro2()
]))
loop.close()
Выходной результат:
wait 1 second
(大约等待1秒)
set_result
data
Здесь объект будущего, за которым следует ожидание, объект будущего может быть вызван после yield from или await в сопрограмме, Его функция состоит в том, чтобы приостановить сопрограмму до тех пор, пока выполнение будущего не завершится, не вернет результат или не вызовет исключение.
И в нашем случае,await future
должен подождатьfuture.set_result('data')
может закончиться только после. будетcoro2()
В качестве второй сопрограммы это может быть недостаточно очевидно, и вызов сопрограммы можно изменить на это:
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait([
# coro1(),
coro2(),
coro1()
]))
loop.close()
Выход остается таким же, как указано выше.
фактически,async
Использование этого ключевого слова может использоваться не только в функциях, но и вasync with
асинхронный менеджер контекста,async for
Асинхронные итераторы. Если вы заинтересованы в них и считаете их полезными, вы можете найти информацию в Интернете. Я не буду здесь слишком много распространяться из-за нехватки места.
Суммировать
В этой статье было сделано некоторое изучение, исследование и краткое изложение генераторов и сопрограмм, но не было проведено много глубоких исследований. Правильно, и в качестве введения к заметке я попытаюсь реализовать асинхронный API самостоятельно, надеясь помочь в понимании и обучении.
Ссылка на ссылку
Сопрограммы Python https://thief.one/2017/02/20/Python%E5%8D%8F%E7%A8%8B/
woohoo.dab EA на .com/co rules/…
How the heck does async/await work in Python 3.5
Документация по сопрограмме Python3.4