I hope that https://py-free-threading.github.io answers the questions you have. If you have specific things you’re looking for that you’d like to see, please feel free to open issues.
Like I get that it means it works, but does it mean all the objects are thread safe or just that they don’t crash when used in multiple threads?
I think we haven’t really settled as a community on exactly what “supporting free-threading” means. This is complicated by the fact that people assume the GIL means their code is thread-safe, when often the opposite is true. Multi-threaded test coverage is also not very good in many packages, including fundamental packages.
Here’s my take:
- Thread safety bugs due to use of mutable global state should definitely be fixed. Those are likely bugs in the GIL-enabled build too but may be hard to trigger in the default configuration.
- Pure functions and immutable objects should definitely be thread-safe, and any lack of thread safety should be fixed.
- You should think about and clearly document what thread safety guarantees you want to provide for any classes defining objects with mutable state in your public API. There are a number of options here and I don’t think there’s a one-size-fits-all solution.
- If you want to guarantee thread-safe operation in all multithreaded contexts, it’s likely you will need to add locking to guarantee that, which may have a performance penalty.
- It’s much cheaper (at least in a language with access to atomic intrinsics or with atomics in the standard library, not sure how this would work in pure-python) to define a flag that is set on an object when a thread takes ownership. If another thread tries to read or write to the object with the flag set, that can be a runtime error. This might make sense for a library wrapping a low-level compression object where a compression context can only be safely accessed by a single thread at a time, for example.
- You could also leave it up to the user of your library to use your library safely. Of course exactly what is safe and isn’t will take some careful thought. Just as an example, right now (and also for a very long time in the GIL-enabled build) it’s pretty easy to crash the interpreter in a multithreaded program using NumPy. We should make it so the interpreter doesn’t crash in situations like that, but getting to the point where that is true will be complicated and there will always be escape hatches via the NumPy C API that we can’t really do anything about. IMO there’s a lot of room for libraries to provide immutable data structures that are thread-safe by construction to avoid issues like this.
IMO, yes, you should add multithreaded tests, even if the free-threaded build wasn’t a thing. Since the threading module exists, your users are free to use your library in a multithreaded context whether or not you would like to support that.
See Validating thread safety with testing - py-free-threading for more guidance on how to write multithreaded tests.
Is there an easy way to see if a requirements list is also free threading safe? Say I use requests from pypi. Do I need to manually check requests and it’s dependencies or do we have tooling to do that?
If it’s a pure-python library, the assumption is that it’s safe in the sense that CPython is safe (e.g. no UB and no data races, but race conditions are possible). Also, single-threaded use will be identical to the GIL-enabled build, so unless you have native extensions you should probably expect existing single-threaded tests to pass.
If a dependency publishes a cp313t wheel on pypi, that is one way of signalling that they support free-threading, but you should probably look through their documentation to what extent they support free-threading, particularly for direct dependencies you want to use in a multi-threaded context.
If you are able to install your library, but at runtime CPython re-enabled the GIL, then you either have a native extension in your library or in a dependency that has not explicitly marked free-threaded support. It’s possible to publish cp313t wheels that re-enable the GIL like this and IMO it’s a valid choice for a library author to do this (although it does make things more difficult for people who want to experiment). You can force the GIL to be disabled with an environment variable or command-line flag to the interpreter.
IMO having more tooling and support in packaging tools to help with this would be great, but right now most of the tracking for this is happening in our tracking table and in @hugovk’s automatically updated tracking table for projects that publish platform-specific wheels.