Are goroutines roughly equivalent to python's asyncio
tasks, with an additional feature that any CPU-bound task is routed to a ThreadPoolExecutor
instead of being added to the event loop (of course, with the assumption that we use a python interpreter without GIL)?
Is there any substantial difference between the two approaches that I'm missing? Of course, apart from the efficiencies and code clarity that result from the concurrency being an integral part of Go.
One of the cool advantages of asyncio is that it scales far better than threading . Each task takes far fewer resources and less time to create than a thread, so creating and running more of them works well. This example just creates a separate task for each site to download, which works out quite well.
asyncio, coroutines, greenlets, etc. have no direct effect on performance and do not help using multiple cores efficiently; asynchronous systems may lead to slower performance (ex.
In Python, asyncio module provides this capability. Multiple tasks can run concurrently on a single thread, which is scheduled on a single CPU core. Although Python supports multithreading, concurrency is limited by the Global Interpreter Lock ( GIL ).
I think I know part of the answer. I tried to summarize my understanding of the differences, in order of importance, between asyncio
tasks and goroutines:
1) Unlike under asyncio
, one rarely needs to worry that their goroutine will block for too long. OTOH, memory sharing across goroutines is akin to memory sharing across threads rather than asyncio
tasks since goroutine execution order guarantees are much weaker (even if the hardware has only a single core).
asyncio
will only switch context on explicit await
, yield
and certain event loop methods, while Go runtime may switch on far more subtle triggers (such as certain function calls). So asyncio
is perfectly cooperative, while goroutines are only mostly cooperative (and the roadmap suggests they will become even less cooperative over time).
A really tight loop (such as with numeric computation) could still block Go runtime (well, the thread it's running on). If it happens, it's going to have less of an impact than in python - unless it occurs in mutliple threads.
2) Goroutines are have off-the-shelf support for parallel computation, which would require a more sophisticated approach under asyncio
.
Go runtime can run threads in parallel (if multiple cores are available), and so it's somewhat similar to running multiple asyncio
event loops in a thread pool under a GIL-less python runtime, with a language-aware load balancer in front.
3) Go runtime will automatically handle blocking syscalls in a separate thread; this needs to be done explicitly under asyncio
(e.g., using run_in_executor
).
That said, in terms of memory cost, goroutines are very much like asyncio
tasks rather than threads.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With