Repository navigation
Conversation
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 <tompytel@gmail.com>
Documentation build overview
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
This makes
array.arraysafe 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
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).len(),a[i],a[i] = xand iteration stay lock-free. A lock-free access loadsob_item, thenob_size, and then checks thatob_itemis unchanged, so the size it uses belongs to the buffer it uses. It falls back to the locked path otherwise.Memory ordering
pyatomic.hhas no 8/16-bit acquire/release operations. 16-byte'Zd'items are accessed as two 8-byte halves and can tear.Changes relative to gh-130771
main(ob_item,allocated), instead of a pointer to a header. Cython'scpython.arraysupport mirrors this struct and managesob_itemitself, so the default build stays compatible. Item buffers also keep their 16-byte alignment.ob_itemafter loading the size. With a stale size a reader could index a new buffer before its items were written and return uninitialized memory.a[i] = xpath, 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-typesetitemfunctions becamepackfunctions that convert into a local buffer, soappend()/insert()also convert only once.*=, slice assignment).a[i]no longer reports a failed item conversion asIndexError, andPySequence_DelItem()raisesIndexErrorfor a bad index again.tofile()rechecks the size after eachwrite()call; it could read out of bounds ifwrite()shrank the array (also with the GIL).__reduce_ex__initializes its cached reconstructor with a compare-and-swap.test_free_threading/test_array.pyand run under TSan.Known limitation
A lock-free
a[i] = xthat 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 thearraydocs, 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):
a[i],a[i] = xand iteration are within 3% ofmain;append()is about 30% faster because the value is converted once.a[i]anda[i] = xare 3-7% faster thanmain,append()is about 20% faster, iteration is about 30% slower because of the lock-free checks, andarray('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 thanmemcpy; arrays used by one thread are not affected.Note: creation of this PR was assisted by an LLM
arraymodule is not free-thread safe. #128942