Free-threaded Python
Past, Present, Future
Thomas Wouters
EuroPython 2026
Who am I?
Thomas Wouters
CPython Core Developer
Steering Council
Meta
Cats
Python
C/C++
Threads
Threads
Thraeds
Threads
Threads
Thraeds twist brian?
Thraeds
Threads
Threads
Waiting on GIL… … … … … … …
When someone forgets to release the GIL
DEADLOCK!!!
Who am I?
Thomas Wouters
CPython Core Developer
Steering Council
Meta
Cats
What are threads?
CPUs, caches, memory
What are threads?
What is the GIL?
struct PyObject {
ssize_t ob_refcnt;
PyTypeObject *ob_type;
}
Python Object Layout
struct PyObject {
PyObject *_ob_next;
PyObject *_ob_prev;
ssize_t ob_refcnt;
PyTypeObject *ob_type;
}
Python Object Layout (with GC)
PyGILState_STATE state = PyGILState_Ensure();
MyObject *myobj = PyList_GetItem(my_global_list, idx);
Py_INCREF(myobj);
myobj->count++;
Py_BEGIN_ALLOW_THREADS
// do something with myobj->data without the GIL held
Py_END_ALLOW_THREADS
Py_DECREF(myobj);
PyGILState_Release(state);
Example Extension Code
What is the GIL?
PyObject *key = PyList_GetItem(mylist, idx);
Py_INCREF(key);
PyObject *result = PyDict_GetItem(mydict, key);
Py_INCREF(result);
Py_DECREF(key);
return result;
Safe Because of the GIL
PyObject *key = PyList_GetItem(mylist, idx);
PyObject *result = PyDict_GetItem(mydict, key);
Py_INCREF(result);
return result;
Unsafe Despite the GIL
PyObject *key = PyList_GetItem(mylist, idx);
Py_INCREF(key);
PyObject *result = PyDict_GetItem(mydict, key);
Py_DECREF(key);
Py_INCREF(result);
return result;
Unsafe Despite the GIL
PyObject *key = PyList_GetItem(mylist, idx);
Py_INCREF(key);
PyObject *result = PyDict_GetItem(mydict, key);
Py_DECREF(key);
Py_INCREF(result);
return result;
Unsafe Despite the GIL
Why remove the GIL at all?
Why remove the GIL at all?
Why is removing the GIL hard?
Why are threads hard?
Thread A
items = list->items
len = list->length
items[len] = item
list->length = len + 1
Interweaving threads
Thread B
items = list->items
len = list->length
items[len] = item
list->length = len + 1
Thread A
items = list->items
len = list->length
items[len] = item
list->length = len + 1
Interweaving threads
Thread B
items = list->items
len = list->length
items[len] = item
list->length = len + 1
Thread A
items = list->items
len = list->length
items[len] = item
list->length = len + 1
Interweaving threads
Thread B
items = list->items
len = list->length
items[len] = item
list->length = len + 1
Thread A
items = list->items
len = list->length
items[len] = item
list->length = len + 1
Interweaving threads
Thread B
items = list->items
len = list->length
items[len] = item
list->length = len + 1
Thread A
items = list->items
len = list->length
items[len] = item
list->length = len + 1
Thread A's Point of View
Thread B
len = list->length
list->length = len + 1
items = list->items
items[len] = item
Thread A
items = list->items
items[len] = item
len = list->length
list->length = len + 1
Thread B's Point of View
Thread B
items = list->items
len = list->length
items[len] = item
list->length = len + 1
Thread A
Thread B
Thread A
items = list->items
items[len] = item
len = list->length
list->length = len + 1
Assignment isn't atomic
Thread B
items = list->items
len = list->length
items[len] = item
list->length = len + 1
Dealing with shared data
Why is removing the GIL hard?
struct PyObject {
PyObject *_ob_next;
PyObject *_ob_prev;
ssize_t ob_refcnt;
PyTypeObject *ob_type;
}
Python C API
Why is removing the GIL hard?
The Past
Free-threaded Python
New allocator and GC
Biased Reference Counting
Deferred Reference Counting
Lock-free dicts and lists
Lock-free dicts and lists
Lock-free dicts and lists
Lock-free dicts and lists
Lock-free dicts and lists
Lock-free dicts and lists
Critical Sections
Stop-the-World
Free-threaded Python
The Present
Migrating your C extensions
PyGILState_STATE state = PyGILState_Ensure();
MyObject *myobj = PyList_GetItemRef(my_global_list, idx);
Py_BEGIN_CRITICAL_SECTION(myobj);
myobj->count++;
Py_END_CRITIAL_SECTION();
Py_BEGIN_ALLOW_THREADS
// do something with myobj->data without the GIL held
Py_END_ALLOW_THREADS
Py_DECREF(myobj);
PyGILState_Release(state);
Migrated Extension Code
Migration tips
Migrating your Python code
Migrating your Python code
Migrating your Python code
The Future
Yhg1s @Yhg1s@social.coop
thomas@python.org twouters@meta.com
Thank You
Atomics (that CPython uses)