Как asyncio.sleep не блокирует поток? - PullRequest
3 голосов
/ 21 июня 2020

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

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

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

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

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

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

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

Ответы [ 2 ]

2 голосов
/ 21 июня 2020

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

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

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

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

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

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

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

1 голос
/ 21 июня 2020

Функция asyncio.sleep просто регистрирует будущее для вызова через x секунд , а 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 не позволяет событию l oop планировать будущее, которое возобновляется sleep_demo. В конце концов, управление возвращается только примерно через 3 секунды.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...