Free-threaded Python – hit or miss?

Veit Schiele

5 September 2025

5–6 minutes

../_images/seams.jpg

Python 3.14 will be released in a few weeks with some important changes regarding parallelism:

Both features represent advances in the use of Python for executing parallel code. But are they threatened with a marginal existence similar to asyncio?

The typical use case for asyncio is web development. Coroutines are well suited for process-independent network calls such as HTTP requests and database queries. Why should the entire Python interpreter be blocked while waiting for a query to be executed on another server? And yet, popular frameworks still do not support asyncio:

This also has to do with some known issues with asyncio:

  • asyncio does not support asynchronous operations on the file system. Even if files are opened with O_NONBLOCK, read and write operations are blocked. One solution to this is to use the aiofiles package, which provides asynchronous file functions.

  • asyncio is not intuitive. For example, what does it mean when code is ‘blocked’, and when is it necessary to switch to threads? Without this basic knowledge, asynchronous code will behave incorrectly, but will not break. Developers therefore do not get the quick feedback they expect from Python.

    ‘An event loop runs in a thread (typically the main thread) and executes all callbacks and Tasks in its thread. While a Task is running in the event loop, no other Tasks can run in the same thread. When a Task executes an `await` expression, the running Task gets suspended, and the event loop executes the next Task.’

    Source: Concurrency and Multithreading

Does free-threaded mode make asyncio more useful or redundant?

Python 3.13 introduced a ‘free-threaded’ version of Python, in which the GIL was removed and replaced with smaller, more granular locks. However, the 3.13 version proved to be insufficiently stable for productive use. In Python 3.14, this now seems to be looking better, and we are now wondering whether we should introduce ‘free-threaded’ in some well-tested projects.

However, supporting both synchronous and asynchronous APIs remains a major challenge. We should also continue to be selective about where asynchrony should be supported. For example, most of the Python standard library does not natively support asynchrony. The dunder methods and __init__ cannot be asynchronous either, so asynchronous network requests cannot be used there.

It may also be necessary to fragment the backend for synchronous and asynchronous operations:

  • requests and httpx are suitable backends for synchronous HTTP calls.

  • aiohttp and httpx are suitable for asynchronous operations.

Since none of these backends are part of the standard Python library, their introduction and support for the most important CPython platforms are not synchronised. For example, aiohttp currently has neither Python 3.14 wheels nor free-threaded support.

Testing your asynchronous code requires different mocks, different calls and, in the case of pytest, a whole range of extensions and patterns for fixtures. This usually becomes very confusing.

See also

async test patterns for Pytest by Anthony Shaw

Summary

The use cases for asyncio remain very limited, and its popularity is therefore very restricted. However, the popularity of the asynchronous web framework FastAPI has continued to grow and currently records over 100 million downloads per month. Considering that asynchronous processes are predominantly used for HTTP and network access, this is more of a success for uvloop, an alternative implementation of the event loop used by FastAPI. It remains to be seen whether Python 3.14, with its support for sub-interpreters and free-threading functions, will make more parallel and concurrent use cases practical and useful.