
On Feb 11, 2020, at 14:02, Eric V. Smith eric@trueblade.com wrote:
One of the goals of the async/await syntax is to make it clear where control can yield, and therefor where you need to be concerned about mutating state. You don't have this explicitness with threads or with a hypothetical syntax that would hide these yield points, so you'd need locks. In my experience, incorrect locking is the source of many hard-to-find bugs with threads.
My own experience converting a smallish gevent project to asyncio was that we only found a few errors—but a ton of unnecessary locks that were the cause of a lot of wasted context switching and other overhead (and debugging annoyance). And I’ve had similar experiences with Windows fibers and longjmp fiberish things in C. That was enough to convince me that the benefits of mandatorily marking switch points isn’t just unnecessary static typing, but a good use of it.
If there is a problem with asyncio (and similar libraries, both in Python and elsewhere), it’s that it forces you to rethink more than just where you can block. For example, you can’t just turn a csock.recv(BUFSIZE) into an await csock.recv(BUFSIZE) because there is no asyncsocket.socket that’s just like socket.socket except its methods are coroutines. But most people aren’t writing most of their code at the lowest level of abstraction; usually you end up, e.g., building a stack of protocol objects that look very similar between twisted and asyncio, even if the lowest level code that you rarely look at is very different. So I think it’s not really a problem for most people.