Add Virtual Threads to Python

My original post may have come across as harsh, so allow me to say that I appreciate the motives of the asyncio authors, and I do think there has been a benefit in terms of learning, exploring concepts, and introducing many others to concurrency. While I do think the consequences of introducing function coloring were evident, it wasn’t obvious how significant an effect it would have. I hold that asyncio has fractured the ecosystem, and has been a net negative (so far), but I’ll allow that one possible way to mend the fracture could be a standard event loop.

That said, and to return to the topic at hand, I think this thread is suggesting that we learn from and move past asyncio and alternatives like gevent to create an even better, and more integrated option. Especially with the prospect of free-threading.

My understanding of virtual threads (as they’re implemented in Java) is that they’re based on continuations (fibers, greenlets, etc…) but provide a traditional threading API and support for structured concurrency. They have no special syntax for context switches, and therefore do not introduce function coloring. Just like gevent, switching happens implicitly on IO or yields (aka sleep). Some call this well-defined, others do not, but it is not explicit.

Sidenote: I found this quote discussing alternatives to virtual threads in JEP 444:

Add syntactic stackless coroutines (i.e., async/await) to the Java language.

It would split the world between APIs designed for threads and APIs designed for coroutines, and would require the new thread-like construct to be introduced into all layers of the platform and its tooling. This would take longer for the ecosystem to adopt, and would not be as elegant and harmonious with the platform as user-mode threads.

So no, virtual threads would not solve the problem of explicit switch points like asyncio does. What it does offer, however, is a lightweight and safer alternative to threads. There is still a pivot point between grokking where the switches happen (I do understand why that makes some people uncomfortable) vs. locking as you would with threads. Personally, I think the uncertainty/learning curve is a price worth paying for cleaner, more readable codebases. That, and not having to write two versions of everything.

As @encukou called out above:

We accept quite a lot of uncertainty in our programs all the time. Perhaps the reason some single out switching from the rest is because it’s hard to understand concurrency when you first encounter it. But it’s understanding concurrency that’s hard, not spotting switch points.

I think it’s worth reviewing all of the following for a deeper understanding of where we are with all of this (not allowed to link them):

  • Unyielding (Glyph, 2014)
    • Why we have asyncio
  • What Color is Your Function? (Bob Nystrom, 2015)
    • Why people don’t like asyncio
  • Pull Push: Please stop polluting our imperative languages with pure concepts-Curry On (Ron Pressler, 2015)
    • Fundamental concepts. Highly recommended
  • Notes on structured concurrency, or: Go statement considered harmful (Nathaniel J. Smith, 2018)
    • Structured concurrency makes asyncio (and other concurrency models) a bit better
  • Playground Wisdom: Threads Beat Async/Await (Armin Ronacher, 2024)
  • From Async/Await to Virtual Threads (Armin Ronacher, 2025)
    • Extensions of the discussion in this thread
4 Likes