Want to learn the ropes of the new "no-GIL" build and true parallelism in Python? Here's where to start. Credit: Heiko Kiera | shutterstock.com The single biggest new feature in Python 3.13 is something Python users have anticipated for ages: a version of Python that allows full concurrency between Python threads by removing the Global Interpreter Lock. Whether you call it “free-threaded” or “no-GIL” Python, the result is the same: a sea change in the way Python handles application parallelism. How do you actually use Python’s new free-threading features? In this article, we’ll walk through how Python 3.13 implements no-GIL mode, how you can use it in your programs now, and how things might change in future versions of Python. Installing Python 3.13’s free-threaded version When Python’s free-threaded version was first announced, the plan wasn’t to immediately replace the GIL-equipped version of Python. Instead, Python users would have the option to install Python’s free-threaded build side-by-side with the regular version and select between them as needed—for example, by way of the py tool in Windows. Binary releases of Python 3.13 for Microsoft Windows and macOS come with an option in the installer to set up the free-threaded build. If you select this option (as shown below), two Python executables are installed: python and python3.13t. The former is the regular build; the latter is the free-threaded build. Python 3.13’s installer lets you install the free-threaded build of Python side-by-side with the regular build. You can use the “py” utility to select which one to use for a given program. IDG On Microsoft Windows, the py tool gives you the option to choose between builds, just as you’d use it to choose an overall version of Python: PS C:\Users\serda> py -0p -V:3.13t C:\Python313\python3.13t.exe -V:3.13 C:\Python313\python.exe -V:3.12 * C:\Python312\python.exe -V:3.11 C:\Python311\python.exe If you run py -3.13, you’ll run the default build: Python 3.13.0 (tags/v3.13.0:60403a5, Oct 7 2024, 09:38:07) [MSC v.1941 64 bit (AMD64)] on win32 Run py -3.13t, and you’ll launch the free-threaded build: Python 3.13.0 experimental free-threading build (tags/v3.13.0:60403a5, Oct 7 2024, 09:53:29) [MSC v.1941 64 bit (AMD64)] on win32 On Linux, the most convenient way to use multiple versions of Python generally is pyenv. A 3.13t or 3.13t-dev option for pyenv lets you install and select that build. (Ubuntu users can also work with the deadsnakes PPA to obtain these builds.) When you use the free-threaded build, the GIL is included in the binary but disabled by default. If for some reason you want to re-enable the GIL in the free-threaded version, you can use the command-line option -X gil=1, or set the environment variable PYTHON_GIL to 1. Using free-threaded Python in your programs If your Python program already uses threading by way of a high-level abstraction like a ThreadPool, you don’t have to change anything. Existing programs that use threads through the CPython APIs run as-is. Here’s a simple example of a program that requires no modification: import time from concurrent.futures import ThreadPoolExecutor as TP def task(): n = 0 for x in range(10_000_000): n+=x return n with TP() as pool: start = time.perf_counter() results = [pool.submit(task) for _ in range(6)] print("Elapsed time:", time.perf_counter() - start) print ([r.result() for r in results]) All threading is handled automatically through the high-level constructs in concurrent.futures. Try running this program with Python 3.12 or Python 3.13 (the GIL version). On my AMD Ryzen 3600 6-core machine, it completes in about two seconds. With Python 3.13t, it completes in about 0.6 seconds—about a three-fold speedup. The exact amount of speedup and how linearly the operations scale will vary depending on the task, but the difference should be most noticeable for CPU-bound operations. Note that most existing Python programs aren’t using threading for CPU-bound operations. They typically use multiprocessing, or the ProcessPoolExecutor abstraction. A version of the above program designed to run well with the GIL would look like this: import time from concurrent.futures import ProcessPoolExecutor as PP def task(): n = 0 for x in range(10_000_000): n+=x return n def main(): with PP() as pool: start = time.perf_counter() results = [pool.submit(task) for _ in range(6)] print("Elapsed time:", time.perf_counter() - start) print ([r.result() for r in results]) if __name__ == "__main__": main() Apart from using a process pool, and having a main() function as an entry point to the main process (for the sake of running properly on Microsoft Windows), it’s the same program. To that end, any program with ThreadPoolExecutor ought to work in no-GIL mode by simply swapping in ProcessPoolExecutor. What’s more, you can do this incrementally— ProcessPoolExecutor still works exactly as-is on Python 3.13t. Checking programmatically for GIL support If you want to write a Python program that detects the presence of the GIL and takes action based on that information, you can do that in a couple of lines: import sys try: GIL_ENABLED = sys._is_gil_enabled() except AttributeError: GIL_ENABLED = True sys._is_gil_enabled(), added in Python 3.13, reports whether or not the GIL is enabled at runtime. It isn’t found on older Python versions, hence our try/except block. Using free-threaded Python with C extensions While “pure” Python programs don’t need much (if any) reworking to use free-threading, C extensions are another story. Any C extensions intended to work specifically with the free-threaded Python builds must be recompiled with support explicitly added for that build. If a given C extension isn’t marked as being free-threaded compatible, the CPython interpreter will automatically enable the GIL unless you used the -X gil=0 option or PYTHON_GIL environment variable to disable it. This is also why it can be useful to programmatically check if the GIL is enabled, so that you can, for instance, decide which version of a module to load. If you’re using Cython to create C extensions, support for free-threaded Python is already being added, but it won’t be available until Cython 3.1 is released. Once that happens, though, you’ll be able to use the directive freethreading_compatible=True to indicate a module is compatible with the free-threaded build. Cython uses the with gil: and with nogil: context managers to mark segments of code that require or run outside the GIL, respectively. If you build Cython modules with free-threading enabled, those context managers effectively get optimized out of the compiled code. In other words, you can continue to use them as needed for code that needs to compile for both GIL-based and free-threaded Python builds. We’ll likely need to keep doing that for a while to come. Related content news Go language evolving for future hardware, AI workloads The Go team is working to adapt Go to large multicore systems, the latest hardware instructions, and the needs of developers of large-scale AI systems. By Paul Krill Nov 15, 2024 3 mins Google Go Generative AI Programming Languages analysis And the #1 Python IDE is . . . PyCharm, VS Code, and five other popular Python IDEs duke it out. Which one do you think takes home the prize? By Serdar Yegulalp Nov 15, 2024 2 mins Python Programming Languages Software Development news JDK 24: The new features in Java 24 21 features are proposed for the next version of Java including quantum-resistant cryptographic keys designed to secure Java apps against future quantum computing attacks. By Paul Krill Nov 15, 2024 11 mins Java Programming Languages Software Development news Rust Foundation moves forward on C++ and Rust interoperability Problem statement released to address the challenges to making cross-language development with C++ and Rust more accessible and approachable. By Paul Krill Nov 14, 2024 2 mins C++ Rust Programming Languages Resources Videos