Issue
I'm trying to follow the examples at https://docs.python.org/3/library/asyncio-task.html#coroutines; here is the code snippet which runs two say_after
coroutines concurrently:
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
# time.sleep(delay)
print(what)
async def main():
task1 = asyncio.create_task(say_after(1, 'hello'))
task2 = asyncio.create_task(say_after(2, 'world'))
print(f"Started at {time.strftime('%X')}")
await task1
await task2
print(f"Finished at {time.strftime('%X')}")
if __name__ == "__main__":
asyncio.run(main())
If I run this, I get that the start and end are two seconds apart:
Started at 12:59:35
hello
world
Finished at 12:59:37
However, if I replace await asyncio.sleep(delay)
with time.sleep(delay)
(the commented-out line in the snippet above), I get that they are three seconds apart, and hence essentially running synchronously:
Started at 13:00:53
hello
world
Finished at 13:00:56
I don't quite understand this; isn't the point of having concurrent tasks that they run in parallel, even if the tasks themselves contain synchronous code? Why does this example no longer work with time.sleep()
instead of asyncio.sleep()
?
Solution
I don't quite understand this; isn't the point of having concurrent tasks that they run in parallel, even if the tasks themselves contain synchronous code?
Concurrency != Parallelism. When writing asyncio
code, the underlying routines still have to yield flow back to the eventloop in order to allow concurrency. And the GIL is still there, regardless.
Why does this example no longer work with time.sleep() instead of asyncio.sleep()?
The difference here is that asyncio.sleep
suspends the current task, allowing other tasks to run. time.sleep
does not, it's a "blocking" call. Programs using asyncio
are still single-threaded (unless otherwise using threading), meaning time.sleep
blocks execution in the main thread and blocks the entire program until the sleep duration has elapsed.
Coroutines afford cooperative concurrency, not parallelism.
To achieve good concurrency through coroutines, any code called within asyncio.run
must be written in a non-blocking way. In practice, it means that any code run within a task has the responsibility to yield when it is a good time to pause execution, e.g. "I'm not doing anything useful because I'm just waiting on I/O" allowing another task to use the event loop.
Answered By - wim
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.