diff --git a/guide/src/free-threading.md b/guide/src/free-threading.md index 77b2ff327a2..6796cebc4e0 100644 --- a/guide/src/free-threading.md +++ b/guide/src/free-threading.md @@ -24,15 +24,18 @@ cannot be sped up using parallelism. The free-threaded build removes this limit on multithreaded Python scaling. This means it's much more straightforward to achieve parallelism using the Python -`threading` module. If you have ever needed to use `multiprocessing` to achieve -a parallel speedup for some Python code, free-threading will likely allow the -use of Python threads instead for the same workflow. +[`threading`](https://docs.python.org/3/library/threading.html) module. If you +have ever needed to use +[`multiprocessing`](https://docs.python.org/3/library/multiprocessing.html) to +achieve a parallel speedup for some Python code, free-threading will likely +allow the use of Python threads instead for the same workflow. PyO3's support for free-threaded Python will enable authoring native Python extensions that are thread-safe by construction, with much stronger safety guarantees than C extensions. Our goal is to enable ["fearless concurrency"](https://doc.rust-lang.org/book/ch16-00-concurrency.html) in the -native Python runtime by building on the Rust `Send` and `Sync` traits. +native Python runtime by building on the Rust [`Send` and +`Sync`](https://doc.rust-lang.org/nomicon/send-and-sync.html) traits. This document provides advice for porting Rust code using PyO3 to run under free-threaded Python. While many simple PyO3 uses, like defining an immutable @@ -45,7 +48,11 @@ We are aware that there are some naming issues in the PyO3 API that make it awkward to think about a runtime environment where there is no GIL. We plan to change the names of these types to de-emphasize the role of the GIL in future versions of PyO3, but for now you should remember that the use of the term `GIL` -in functions and types like `with_gil` and `GILOnceCell` is historical. +in functions and types like +[`Python::with_gil`](https://docs.rs/pyo3/latest/pyo3/marker/struct.Python.html#method.with_gil) +and +[`GILOnceCell`](https://docs.rs/pyo3/latest/pyo3/sync/struct.GILOnceCell.html) +is historical. Instead, you can think about whether or not a Rust thread is attached to a Python interpreter runtime. See [PEP @@ -64,20 +71,23 @@ The main reason for attaching to the Python runtime is to interact with Python objects or call into the CPython C API. To interact with the Python runtime, the thread must register itself by attaching to the interpreter runtime. If you are not yet attached to the Python runtime, you can register the thread using the -[`Python::with_gil`] function. Threads created via the Python `threading` module -do not not need to do this, but all other OS threads that interact with the -Python runtime must explicitly attach using `with_gil` and obtain a `'py` -liftime. - -In the GIL-enabled build, PyO3 uses the `Python<'py>` type and the `'py` lifetime -to signify that the global interpreter lock is held. In the freethreaded build, -holding a `'py` lifetime means the thread is currently attached to the Python -interpreter but other threads might be simultaneously interacting with the -Python runtime. +[`Python::with_gil`](https://docs.rs/pyo3/latest/pyo3/marker/struct.Python.html#method.with_gil) +function. Threads created via the Python +[`threading`](https://docs.python.org/3/library/threading.html) module do not +not need to do this, but all other OS threads that interact with the Python +runtime must explicitly attach using `with_gil` and obtain a `'py` liftime. + +In the GIL-enabled build, PyO3 uses the +[`Python<'py>`](https://docs.rs/pyo3/latest/pyo3/marker/struct.Python.html) type +and the `'py` lifetime to signify that the global interpreter lock is held. In +the freethreaded build, holding a `'py` lifetime means only that the thread is +currently attached to the Python interpreter -- other threads can be +simultaneously interacting with the interpreter. Since there is no GIL in the free-threaded build, releasing the GIL for long-running tasks is no longer necessary to ensure other threads run, but you -should still detach from the interpreter runtime using [`Python::allow_threads`] +should still detach from the interpreter runtime using +[`Python::allow_threads`](https://docs.rs/pyo3/latest/pyo3/marker/struct.Python.html#method.allow_threads) when doing long-running tasks that do not require the CPython runtime. The garbage collector can only run if all threads are detached from the runtime (in a stop-the-world state), so detaching from the runtime allows freeing unused @@ -89,7 +99,9 @@ Data attached to `pyclass` instances is protected from concurrent access by a `RefCell`-like pattern of runtime borrow checking. Like a `RefCell`, PyO3 will raise exceptions (or in some cases panic) to enforce exclusive access for mutable borrows. It was always possible to generate panics like this in PyO3 in -code that releases the GIL with `allow_threads` or caling a `pymethod` accepting +code that releases the GIL with +[`Python::allow_threads`](https://docs.rs/pyo3/latest/pyo3/marker/struct.Python.html#method.allow_threads) +or calling a python method accepting `&self` from a `&mut self` (see [the docs on interior mutability](./class.md#bound-and-interior-mutability),) but now in free-threaded Python there are more opportunities to trigger these panics from Python because @@ -97,7 +109,8 @@ there is no GIL to lock concurrent access to mutably borrowed data from Python. The most straightforward way to trigger this problem to use the Python `threading` module to simultaneously call a rust function that mutably borrows a -`pyclass`. For example, consider the following `PyClass` implementation: +[`pyclass`](https://docs.rs/pyo3/latest/pyo3/attr.pyclass.html). For example, +consider the following implementation: ``` # use pyo3::prelude::*; @@ -154,11 +167,14 @@ needed. ## `GILProtected` is not exposed -`GILProtected` is a PyO3 type that allows mutable access to static data by -leveraging the GIL to lock concurrent access from other threads. In -free-threaded Python there is no GIL, so you will need to replace this type with -some other form of locking. In many cases, a type from `std::sync::atomic` or -a `std::sync::Mutex` will be sufficient. +[`GILProtected`](https://docs.rs/pyo3/latest/pyo3/sync/struct.GILProtected.html) +is a PyO3 type that allows mutable access to static data by leveraging the GIL +to lock concurrent access from other threads. In free-threaded Python there is +no GIL, so you will need to replace this type with some other form of +locking. In many cases, a type from +[`std::sync::atomic`](https://doc.rust-lang.org/std/sync/atomic/) or a +[`std::sync::Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html) will +be sufficient. Before: @@ -209,5 +225,6 @@ builds and mutexes otherwise. If your use of `GILProtected` does not guard the execution of arbitrary Python code or use of the CPython C API, then conditional compilation is likely unnecessary since `GILProtected` was not needed in the first place and instead Rust mutexes or atomics should be preferred. Python 3.13 -introduces `PyMutex`, which releases the GIL while the waiting for the lock, so -that is another option if you only need to support newer Python versions. +introduces [`PyMutex`](https://docs.python.org/3/c-api/init.html#c.PyMutex), +which releases the GIL while the waiting for the lock, so that is another option +if you only need to support newer Python versions.