GeezerVison is showing github.com — gh-128942: Make array.array thread-safe in the free-threaded build by nascheme · Pull Request #161787 · python/cpython
Conversation
nascheme
commented
Nov 10, 2026
•
edited by bedevere-app
Bot
Loading
Uh oh!
There was an error while loading. Please reload this page.
This makes array.array safe to share between threads in the free-threaded build, without the slowdown that caused the earlier gh-128943 change to be reverted (75f38af, scimark regressions).
It is based on gh-130771 by Tomasz Pytel (tom-pytel), who did the bulk of the design, and supersedes it. He is listed as co-author.
Approach
- Same design as
list. The layout of the array object is unchanged. In the free-threaded build the capacity is also stored in a header in front of the items. Once the array is shared between threads, old buffers are freed with QSBR (_PyMem_FreeDelayed, now exported for the module).
- All mutating and multi-item methods run in the object's critical section; methods on two arrays lock both.
len(), a[i], a[i] = x and iteration stay lock-free. A lock-free access loads ob_item, then ob_size, and then checks that ob_item is unchanged, so the size it uses belongs to the buffer it uses. It falls back to the locked path otherwise.
Memory ordering
- The length uses release stores and acquire loads, and items use acquire loads and release stores. A reader that observes a larger length also observes the new items, because growing operations write the items before publishing the length.
- 1- and 2-byte items use a relaxed access plus an acquire or release fence, since
pyatomic.h has no 8/16-bit acquire/release operations. 16-byte 'Zd' items are accessed as two 8-byte halves and can tear.
- Bulk copies use per-item atomic accesses unless the array is owned by the current thread and not shared, so there are no data races with lock-free readers. TSan is clean.
Changes relative to gh-130771
- The object layout stays as it is on
main (ob_item, allocated), instead of a pointer to a header. Cython's cpython.array support mirrors this struct and manages ob_item itself, so the default build stays compatible. Item buffers also keep their 16-byte alignment.
- The lock-free paths re-check
ob_item after loading the size. With a stale size a reader could index a new buffer before its items were written and return uninitialized memory.
- In the lock-free
a[i] = x path, the value is converted before the buffer is loaded. Before, __index__/__float__ ran after the load and could let QSBR free the buffer before the store. The per-type setitem functions became pack functions that convert into a local buffer, so append()/insert() also convert only once.
- Release/acquire on the length and items instead of relaxed and plain accesses.
- Items are written before a larger length is published (append, extend, frombytes, fromlist, fromunicode,
*=, slice assignment).
a[i] no longer reports a failed item conversion as IndexError, and PySequence_DelItem() raises IndexError for a bad index again.
tofile() rechecks the size after each write() call; it could read out of bounds if write() shrank the array (also with the GIL).
__reduce_ex__ initializes its cached reconstructor with a compare-and-swap.
- The free-threading tests are in
test_free_threading/test_array.py and run under TSan.
Known limitation
A lock-free a[i] = x that races with an operation that resizes or rearranges the same array can be lost; for a resize the store can land in the old buffer after it was copied. This is accepted to keep item stores lock-free; there is no crash and no corruption. It is documented in the new "Thread safety" section of the array docs, together with 'Zd' tearing.
Performance
pyperformance scimark (x86-64): no slowdown; geomean 1.01x faster in the default build and 1.03x faster in the free-threaded build.
Microbenchmarks (x86-64, 1M items):
- Default build:
a[i], a[i] = x and iteration are within 3% of main; append() is about 30% faster because the value is converted once.
- Free-threaded build:
a[i] and a[i] = x are 3-7% faster than main, append() is about 20% faster, iteration is about 30% slower because of the lock-free checks, and array('d', list) is about 9% slower. Bulk copies of a 'B' array that is shared between threads use per-item atomics and are 1.5-4x slower than memcpy; arrays used by one thread are not affected.
Note: creation of this PR was assisted by an LLM
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Based on pythongh-130771. Keep single-item reads and writes lock-free,
serialize mutating and multi-item operations, and reclaim shared
buffers with QSBR. Preserve the object layout and default-build
performance.
Validate the buffer and length together for lock-free access, and
convert values before loading the buffer when assignment can run user
code. Concurrent assignments racing with a resize may still be lost,
and 16-byte complex items can tear.
Add free-threading tests and document thread-safety guarantees.
Co-authored-by: Tomasz Pytel <[email protected]>
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Reviewers
ericsnowcurrently
Awaiting requested review from ericsnowcurrently
ericsnowcurrently is a code owner
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Milestone
No milestone
Development
Successfully merging this pull request may close these issues.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|