Free-Threading (PEP 703)¶
PyOZ supports free-threaded CPython builds (python3.13t, python3.14t).
Declaring GIL-free support¶
pub const Module = pyoz.module(.{
.name = "mymod",
.gil_used = false, // this module is safe without the GIL
// ...
});
Without .gil_used = false, importing the module on a free-threaded
interpreter re-enables the GIL for the whole process (CPython prints a
RuntimeWarning). The option is opt-in because it is a promise that your
module's own code is thread-safe. It has no effect on regular builds.
What PyOZ does for you¶
- Per-object locking. On free-threaded builds every method, property
getter/setter and protocol slot of a PyOZ class runs inside a CPython
critical section on
self(binary operators and comparisons lock both operands, deadlock-free). Two threads calling into the same object can no longer corrupt its Zig state. As with CPython's own objects, the lock protects one operation, and it is suspended while the thread blocks or releases the GIL (pyoz.releaseGIL). - Cost: about 9–10 ns per call on free-threaded builds; exactly zero on regular builds, where the wrapper is removed at compile time.
- Internal caches (datetime, decimal, pathlib) use lock-free initialization.
__freelist__is ignored on free-threaded builds (the per-thread allocator of free-threaded CPython plays that role).
Testing¶
PyOZ's own suite includes free-threading stress tests (many threads mutating
one object, parallel event loops running async jobs). They run on multi-core CI
runners against 3.14t in Debug and ReleaseSafe. Test your own classes the same
way: build against a free-threaded interpreter, check
sys._is_gil_enabled() is False after import, and hammer shared objects from
several threads.
Opting out of locking¶
Immutable classes, or classes that synchronize internally, can skip the lock:
Module-level functions are not locked: shared global state they touch must be
synchronized by you (e.g. with std.atomic or a mutex).
Building and packaging¶
pyoz build detects a free-threaded interpreter and produces a
cpXY-cpXYt wheel (e.g. cp314-cp314t). abi3 = true is rejected there: the
Stable ABI does not cover free-threaded builds.
Next Steps¶
- Async - Awaitable Zig functions and methods
- GIL Management - Releasing the GIL and its interaction with object locks
- Classes - Frozen classes and class options