I look forward to reading your Django/Flask take. I have long found that after a Flask project grows just a little bit, I wish I'd started with Django, as I like Django's coherence across the stack and find it frustrating to piece together a Flask application from various extensions and libraries.
Good article. I find myself mostly agreeing with it. I inherited some code written with asyncio and immediately refactored it out. After losing some performance I introduced some ThreadPoolExecutors and gained most of it back. The simplified codebase was then much easier to work with and understand that I was able to make other performance changes and exceed original performance.
I do like Golang's concurrency model. I like how you can write a single function and invoke it either synchronously or asynchronously... and doing so is a matter of either calling `foo()` or `go foo()`. In Python functions can be async or not, but they can't be both... and trying to synchronously call an async function and get the result back is probably like 5 lines of code.
I think you jumped to this conclusion too quickly:
> In fact, I would dare to say that the vast majority of developers are not working on problems where network performance is an issue that hasn't be solved in a better way. At least at their scale.
async io is not about how fast can particular task finish there, but how many requests can you serve using the same hardware. If your threads are blocking while waiting for IO to finish you cannot handle any more requests in the same time
Maybe to clarify a bit. You mentioned that asyncio is used exactly for those purposes, but i find this part
> Note that they only can speed up waiting on the network. They will not make two calculations at the same time (can't use several CPU cores like with multiprocessing) and you can't speed up waiting on other types of I/O (like when you use threads to not block on user input or disk writes).
The DB interface is using sockets, so the network.
The file system interface is delegating I/O to threads.
None of them use asyncio to deal with files because there is not a consistent async API for FS access accross OSes. In fact, even libuv uses Thread under the hood because of this.
This distinction is important, as with coroutines, you can spawn 100000 and just let the event loop deal with it. With threads, asyncio will use run_in_executor, which means you will be limited by the number of workers you assigned to the pool.
It's not really the topic of the article though, so I didn't get into those details.
Cool, nice article. I always found the whole "async" overhyped since the day it came out. It was always "This is an awesome library that will save you a lot of time" followed by a lot of toy examples I just knew would never scale.
And speaking of hype-- FastApi. Never understand the hype around it. Still not sure why I'd use it over Flask or Django with a good REST app.
Regards your comment about beginners should start with Django-- would love to hear more!
What about the move to remove the GIL in 3.13? I am excited and hopeful about being able to move away from all of this async code and towards that. Async seems even more confusing than threads.
Removing the GIL is good for the form of concurrency where you share a CPU for calculation purposes. However, it's not going to improve the network story much.
Great article.
I look forward to reading your Django/Flask take. I have long found that after a Flask project grows just a little bit, I wish I'd started with Django, as I like Django's coherence across the stack and find it frustrating to piece together a Flask application from various extensions and libraries.
Good article. I find myself mostly agreeing with it. I inherited some code written with asyncio and immediately refactored it out. After losing some performance I introduced some ThreadPoolExecutors and gained most of it back. The simplified codebase was then much easier to work with and understand that I was able to make other performance changes and exceed original performance.
I do like Golang's concurrency model. I like how you can write a single function and invoke it either synchronously or asynchronously... and doing so is a matter of either calling `foo()` or `go foo()`. In Python functions can be async or not, but they can't be both... and trying to synchronously call an async function and get the result back is probably like 5 lines of code.
I think you jumped to this conclusion too quickly:
> In fact, I would dare to say that the vast majority of developers are not working on problems where network performance is an issue that hasn't be solved in a better way. At least at their scale.
async io is not about how fast can particular task finish there, but how many requests can you serve using the same hardware. If your threads are blocking while waiting for IO to finish you cannot handle any more requests in the same time
It's the same thing, seen from a different view point.
Maybe to clarify a bit. You mentioned that asyncio is used exactly for those purposes, but i find this part
> Note that they only can speed up waiting on the network. They will not make two calculations at the same time (can't use several CPU cores like with multiprocessing) and you can't speed up waiting on other types of I/O (like when you use threads to not block on user input or disk writes).
misleading. There is an asyncio interface for databases https://github.com/encode/databases and for the filesystem io
https://github.com/Tinche/aiofiles/
The DB interface is using sockets, so the network.
The file system interface is delegating I/O to threads.
None of them use asyncio to deal with files because there is not a consistent async API for FS access accross OSes. In fact, even libuv uses Thread under the hood because of this.
This distinction is important, as with coroutines, you can spawn 100000 and just let the event loop deal with it. With threads, asyncio will use run_in_executor, which means you will be limited by the number of workers you assigned to the pool.
It's not really the topic of the article though, so I didn't get into those details.
Cool, nice article. I always found the whole "async" overhyped since the day it came out. It was always "This is an awesome library that will save you a lot of time" followed by a lot of toy examples I just knew would never scale.
And speaking of hype-- FastApi. Never understand the hype around it. Still not sure why I'd use it over Flask or Django with a good REST app.
Regards your comment about beginners should start with Django-- would love to hear more!
It's in my list of things to write about. A rather long list however :)
What about the move to remove the GIL in 3.13? I am excited and hopeful about being able to move away from all of this async code and towards that. Async seems even more confusing than threads.
Removing the GIL is good for the form of concurrency where you share a CPU for calculation purposes. However, it's not going to improve the network story much.
Another good piece of writing. This is a nice summary of the async ecosystem. 🥳
When I need async stuff, I prefer to use anyio, and when web si involved I use Django-ninja-extras (it is Django-ninja with some nice extras)