Как asyncio.sleep не блокирует поток?

Я снова и снова читаю «Свободный Python» Лучано Рамальо, но не мог понять поведения asyncio.sleep внутри asyncio.

В одной части книги говорится:

Никогда не используйте time.sleep в сопрограммах asyncio, если вы не хотите блокировать основной поток, что приводит к замораживанию цикла событий и, возможно, всего приложения. (...) он должен выходить из asyncio.sleep (DELAY).

С другой стороны:

Каждая функция блокирующего ввода-вывода в стандартной библиотеке Python освобождает GIL (...). Функция time.sleep () также освобождает GIL.

Поскольку time.sleep () выпускает коды GIL в другом потоке, может выполняться, но блокирует текущий поток. Поскольку asyncio является однопоточным, я понимаю, что time.sleep блокирует цикл asyncio.

Но как asyncio.sleep () не блокирует поток? Можно ли не откладывать цикл событий и одновременно ждать?


person jupiterbjy    schedule 21.06.2020    source источник
comment
asyncio.sleep() на самом деле не спит. Он возвращает управление и планирует повторный вызов для продолжения. Он больше похож на yield.   -  person Klaus D.    schedule 21.06.2020
comment
Возможно, это поможет.   -  person cglacet    schedule 21.06.2020
comment
@KlausD. Эта одна фраза имела большой смысл, очень ценилась. Не лучше ли закрыть этот вопрос сейчас?   -  person jupiterbjy    schedule 21.06.2020
comment
@cglacet У меня уже были проблемы с модулем asyncio с pycharm. Это мой мозг не мог понять, как это работает в целом.   -  person jupiterbjy    schedule 21.06.2020
comment
Важным элементом этого кода является call_later. . Вместо time.sleep, который приостанавливает выполнение на x секунды, asyncio.sleep просто регистрирует будущее, которое будет вызываться через x секунды (а также передает управление обратно цикл обработки событий).   -  person cglacet    schedule 21.06.2020


Ответы (2)


Функция asyncio.sleep просто регистрирует будущее, которое будет вызываться через x секунды < / a>, а time.sleep приостанавливает выполнение на x секунды.

Вы можете проверить, как оба ведут себя на этом небольшом примере, и увидеть, как asyncio.sleep(1) на самом деле не дает вам подсказки о том, как долго он будет спать, потому что это не то, что он на самом деле делает:

import asyncio 
import time
from datetime import datetime


async def sleep_demo():
    print("sleep_demo start: ", datetime.now().time())
    await asyncio.sleep(1)
    print("sleep_demo end: ", datetime.now().time())
    

async def I_block_everyone():
    print("I_block_everyone start: ", datetime.now().time())
    time.sleep(3)
    print("I_block_everyone end: ", datetime.now().time())
    
    
asyncio.gather(*[sleep_demo(), I_block_everyone()])

Это печатает:

sleep_demo start:  04:46:55.902913
I_block_everyone start:  04:46:55.903119
I_block_everyone end:  04:46:58.905383
sleep_demo end:  04:46:58.906038

Вызов блокировки time.sleep не позволяет циклу событий планировать будущее, которое возобновляется sleep_demo. В конце концов, он получает контроль только примерно через 3 секунды.

Теперь, что касается функции time.sleep(), которая также освобождает GIL., Это не противоречие, поскольку она разрешает выполнение только другого потока (но текущий поток будет оставаться в ожидании в течение x секунд). В некоторой степени оба выглядят немного похожими: в одном случае GIL освобождается, чтобы освободить место для другого потока, в asyncio.sleep цикл событий получает контроль, чтобы запланировать другую задачу.

person cglacet    schedule 21.06.2020

Под капотом asyncio есть цикл событий: это функция, которая перебирает очередь задач. Когда вы добавляете новую задачу, она добавляется в очередь. Когда задача завершается, она приостанавливается, и цикл обработки событий переходит к следующей задаче. Приостановленные задачи игнорируются, пока они не возобновятся. Когда задача завершается, она удаляется из очереди.

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

Несколько цитат из официальной документации:

Цикл событий - это ядро ​​любого приложения asyncio. Циклы событий запускают асинхронные задачи и обратные вызовы, выполняют сетевые операции ввода-вывода и запускают подпроцессы.

Циклы событий используют совместное планирование: цикл событий запускает одну задачу за раз. Пока Задача ожидает завершения Будущего, цикл событий запускает другие Задачи, обратные вызовы или выполняет операции ввода-вывода.

Когда вы вызываете asyncio.sleep, он приостанавливает текущую задачу, позволяя запускать другие задачи. Что ж, я в основном пересказываю документацию:

sleep () всегда приостанавливает текущую задачу, позволяя запускать другие задачи.

person Shadows In Rain    schedule 21.06.2020