GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-127716: make memoryview thread-safe in free-threaded build by nascheme · Pull Request #156248 · python/cpython

Conversation

@nascheme

@nascheme nascheme commented Aug 23, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

Note: this PR was developed with assistance from LLMs, Claude Opus/Fable and GPT 5.6 Sol.

This incorporates changes from gh-155882 and gh-154770. It goes further and ensures the underlying buffer remains "pinned" if a thread releases the memory view while other threads have in-flight operations on it.

Performance testing results are below. The goal is negligible slowdown for the GIL-enabled build (achieved). Also, we want negligible slowdown in the un-contended free-threaded case (also achieved). Unfortunately, multi-threaded scaling with a shared memoryview object is terrible.

I explored various ways of improving the scaling (QSBR, RCU, hazard pointers) but I think other options are too complex in terms of implementation or have other critical problems. If you want multiple threads operating on the same underlying "mbuf", a good way to do it is to create a thread-local memoryview copy. You need to do this from the orginal "exporter" object, not the existing memoryview object. I have an additional change that adds a .reexport() method that does this, which is probably handly.

Single-thread performance

Times are nanoseconds per operation (lower is better), minus loop overhead; iteration is
nanoseconds per element. "base" is before this PR and "patched" is after.

Results from Intel i7-14700K

Benchmark base FT patched FT ratio base GIL patched GIL ratio
getitem 14.65 20.54 1.40x 16.00 15.45 0.97x
setitem 19.33 22.33 1.16x 19.17 19.52 1.02x
format 17.42 20.19 1.16x 17.88 17.76 0.99x
slice 33.36 53.38 1.60x 34.10 33.59 0.99x
cast 33.31 53.39 1.60x 35.02 34.01 0.97x
tobytes_16 30.87 28.99 0.94x 29.91 27.04 0.90x
memoryview_of_view 42.64 59.11 1.39x 41.88 41.42 0.99x
struct.unpack_from 48.83 55.51 1.14x 51.15 50.20 0.98x
BytesIO.readinto 59.57 76.09 1.28x 40.55 39.95 0.99x
BytesIO.write 63.15 76.51 1.21x 22.64 21.85 0.97x

Results from Macbook M3 Pro.

Benchmark base FT patched FT ratio
getitem 8.36 9.35 1.12x
setitem 10.71 11.00 1.03x
format 10.11 11.62 1.15x
slice 23.11 25.37 1.10x
cast 23.88 24.21 1.01x
tobytes_16 18.71 21.10 1.13x
memoryview_of_view 30.60 30.84 1.01x
unpack_from 27.16 30.20 1.11x
bytesio_write 22.12 24.90 1.13x
bytesio_readinto 22.19 24.37 1.10x

Free-threaded scaling

Throughput is millions of operations per second (higher is better). For iteration, one yielded
element is one operation. Base has no reexport(), so that workload has a
patched row only; compare it against the separate views of one exporter rows.

Results from Intel i7-14700K

Workload Build 1 thread 2 threads 4 threads 8 threads 8-thread patched/base
shared view, mv[0] base 40.4 79.5 157.5 308.1
patched 32.9 11.5 10.1 10.7 0.03x
slices of one shared view, mv[0] base 40.1 79.8 157.4 313.3
patched 32.9 16.7 13.7 10.1 0.03x
separate views of one exporter, mv[0] base 40.3 79.9 157.0 313.4
patched 33.0 65.9 130.1 259.7 0.83x
per-thread reexport() views, mv[0] patched 33.0 66.1 130.6 253.4 n/a
private views and exporters, mv[0] base 40.5 80.2 158.1 313.5
patched 32.9 66.0 130.2 250.0 0.80x
shared view, iterate 16 bytes base 82.4 55.7 61.7 60.1
patched 30.0 5.2 5.4 4.9 0.08x
shared view, tobytes() 4 KiB base 10.6 21.7 42.7 79.6
patched 10.4 9.2 12.5 9.4 0.12x
shared view, struct.unpack_from base 14.9 3.9 4.6 5.0
patched 12.5 3.2 3.2 2.1 0.42x

Results from Macbook M3 Pro. Note that this CPU has 6 P-cores and 6 E-cores.

Workload Build 1 thread 2 threads 4 threads 8 threads 4-thread patched/base
shared view, mv[0] base 73.1 144.8 282.3 321.9
patched 68.7 28.7 22.6 10.0 0.080x
slices of one shared view, mv[0] base 73.2 144.0 279.9 320.6
patched 68.4 29.0 23.0 10.0 0.082x
separate views of one exporter, mv[0] base 73.7 143.8 278.4 298.3
patched 68.2 133.8 260.8 295.1 0.94x
per-thread reexport() views, mv[0] patched 68.4 133.8 261.5 276.4 n/a
private views and exporters, mv[0] base 70.1 139.2 275.5 314.8
patched 68.4 134.2 261.6 287.4 0.95x
shared view, iterate 16 bytes base 146.3 177.0 99.1 34.1
patched 106.7 16.4 13.5 4.3 0.14x
shared view, tobytes() 4 KiB base 11.4 22.4 42.0 51.5
patched 11.0 12.8 15.8 9.7 0.38x
shared view, struct.unpack_from base 28.4 9.7 9.2 3.8
patched 27.2 8.0 7.3 5.6 0.79x

Anything that shares one managed buffer contends (shared mbuf->exports cache line).

  • Issue: make memoryview thread safe in free-threading #127716

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

a-reich reacted with heart emoji
All reactions
  • ❤️ 1 reaction
@nascheme nascheme added the topic-free-threading label Aug 23, 2026
@bedevere-app bedevere-app Bot mentioned this pull request Aug 23, 2026
make memoryview thread safe in free-threading #127716
Closed
@read-the-docs-community

read-the-docs-community Bot commented Aug 23, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

Documentation build overview

📚 cpython-previews | 🛠️ Build #35545131 | 📁 Comparing 8833436 against main (b062727)

  🔍 Preview build  

427 files changed · + 10 added · ± 417 modified

+ Added

  • builtins/constants.html
  • builtins/exceptions.html
  • builtins/functions.html
  • builtins/index.html
  • builtins/stdtypes.html
  • builtins/threadsafety.html
  • builtins/time-complexity.html
  • c-api/pyexpat.html
  • library/time-complexity.html
  • library/tkinter.dialogs.html

± Modified

  • glossary.html
  • index.html
  • license.html
  • py-modindex.html
  • _static/tachyon-example-heatmap.html
  • c-api/arg.html
  • c-api/bool.html
  • c-api/buffer.html
  • c-api/bytearray.html
  • c-api/bytes.html
  • and 407 more...
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@weixlu

weixlu commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Hi, thanks for the thorough fix! I read the code and it looks like all operations of the counters are well handled, so I'll close my PR.

Also, I checkout your branch and run the reproducers from #127716 and #155606. Both issues appear to be resolved.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@weixlu weixlu mentioned this pull request Aug 24, 2026
gh-127716: Fix data race in memoryview.release() #154770
Closed
@weixlu @ayaangazali @nascheme
pythongh-127716: make memoryview thread-safe
8fa2daf
Co-authored-by: ayaangazali <[email protected]>
Co-authored-by: Lu Xiaowei <[email protected]>
@nascheme
nascheme force-pushed the gh-127716-memoryview-thread-safe branch from 4269e2d to 8fa2daf Compare August 25, 2026 16:24
nascheme added 3 commits August 25, 2026 18:55
@nascheme
Fix two bugs.
fb7e672
* The CHECK_* macros can return.  Restructure code so that unpin is
  always done for toreadonly().

* Use atomic load for asserts on `exports`.
@nascheme
Unit test improvements.
908a2a1
Speed it up (less iterations, rounds and threads).  Rename confusing
assert_exporter_free() method to ensure_exporter_free().
@nascheme
Merge remote-tracking branch 'origin/main' into pythongh-127716-memor…
dd6c824
…yview-thread-safe
@nascheme
nascheme marked this pull request as ready for review August 27, 2026 20:22
@bedevere-app bedevere-app Bot added the awaiting core review label Aug 27, 2026
@nascheme
nascheme requested a review from kumaraditya303 August 27, 2026 20:23
kumaraditya303
kumaraditya303 reviewed Aug 28, 2026
View reviewed changes
Comment thread Objects/memoryobject.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

kumaraditya303
kumaraditya303 reviewed Aug 28, 2026
View reviewed changes
Comment thread Objects/memoryobject.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

nascheme added 3 commits August 28, 2026 08:59
@nascheme
Address review feedback.
6692fa5
* Add comment to CAS while loop, yielding is unnecessary.
* Use "relaxed" memory ordering, seq-cst is overkill.
@nascheme
Merge remote-tracking branch 'origin/main' into pythongh-127716-memor…
ab2d602
…yview-thread-safe
@nascheme
Remove "while" loop for setting flags.
e3c9012
We only need the "while" loop if we are setting multiple flag bits
concurrently.  That's not needed, we only set RELEASED.  So, restructure
code, no while loop needed and someone can't accidently call the
function in the wrong way.
@maurycy

maurycy commented Aug 31, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

@nascheme What do you think about making the iterator hold exports for its whole lifetime, instead of pinning in memoryiter_next()? I believe this should remove the performance penalty, at the expense of granularity.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@nascheme

nascheme commented Aug 31, 2026

Copy link
Copy Markdown
Member Author

Increasing performance would be nice but that would change the behaviour of memoryview.release(). I.e. you couldn't call it as long as an iterator is alive. So, I think we should not make that change.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@maurycy

maurycy commented Aug 31, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Increasing performance would be nice but that would change the behaviour of memoryview.release(). I.e. you couldn't call it as long as an iterator is alive. So, I think we should not make that change.

I mean mbuf->exports, not self->exports. Sorry for not being clear.

memoryview.release() checks self->exports:

cpython/Objects/memoryobject.c

Lines 1253 to 1263 in e3c9012

Py_BEGIN_CRITICAL_SECTION(self);
Py_ssize_t exports = get_exports(self);
if (exports == 0) {
_memory_release(self);
result = Py_NewRef(Py_None);
}
else if (exports > 0) {
PyErr_Format(PyExc_BufferError,
"memoryview has %zd exported buffer%s", exports,
exports == 1 ? "" : "s");
}

What I'm thinking (quick Sol 5.6-assisted proof of concept patch to demonstrate the idea):

diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c
index 2d8537ee57..b66f3b9153 100644
--- a/Objects/memoryobject.c
+++ b/Objects/memoryobject.c
@@ -3946,6 +3946,11 @@ memoryiter_dealloc(PyObject *self)
 {
     memoryiterobject *it = (memoryiterobject *)self;
     _PyObject_GC_UNTRACK(it);
+#ifdef Py_GIL_DISABLED
+    if (it->it_seq != NULL) {
+        mbuf_drop_export(it->it_seq->mbuf);
+    }
+#endif
     Py_XDECREF(it->it_seq);
     PyObject_GC_Del(it);
 }
@@ -3969,33 +3974,28 @@ memoryiter_next(PyObject *self)

     if (it->it_index >= it->it_length) {
         /* Preserve StopIteration for exhausted iterators over relea */
+#ifndef Py_GIL_DISABLED
         it->it_seq = NULL;
         Py_DECREF(seq);
+#endif
         return NULL;
     }

-#ifdef Py_GIL_DISABLED
-    /* Keep seq alive if another thread exhausts the iterator. */
-    Py_INCREF(seq);
-#endif
+    CHECK_RELEASED(seq);

-    PyObject *result = NULL;
-    if (memoryview_pin(seq) == 0) {
-        Py_buffer *view = &seq->view;
-        char *ptr = (char *)view->buf;
+    Py_buffer *view = &seq->view;
+    char *ptr = (char *)view->buf;

-        ptr += view->strides[0] * it->it_index++;
-        ptr = ADJUST_PTR(ptr, view->suboffsets, 0);
-        if (ptr != NULL) {
-            result = unpack_single(seq, ptr, it->it_fmt);
-        }
-        memoryview_unpin(seq);
+    ptr += view->strides[0] * it->it_index++;
+    ptr = ADJUST_PTR(ptr, view->suboffsets, 0);
+    if (ptr == NULL) {
+        return NULL;
     }
-
-#ifdef Py_GIL_DISABLED
-    Py_DECREF(seq);
-#endif
-    return result;
+    return unpack_single(seq, ptr, it->it_fmt);
 }

 static PyObject *
@@ -4032,6 +4032,11 @@ memory_iter_pinned(PyObject *seq)
     it->it_length = memory_length((PyObject *)obj);
     it->it_index = 0;
     it->it_seq = (PyMemoryViewObject*)Py_NewRef(obj);
+#ifdef Py_GIL_DISABLED
+    FT_ATOMIC_ADD_SSIZE(obj->mbuf->exports, 1);
+#endif
     _PyObject_GC_TRACK(it);
     return (PyObject *)it;
 }

The downside is that one won't be able to resize a bytearray while iter still exists:

ba = bytearray(128); m = memoryview(ba); it = iter(m)
m.release()
ba.append(1)  # would raise
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@nascheme

nascheme commented Sep 1, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

I mean mbuf->exports, not self->exports.

I see. That still changes the release semantics as your example shows. My guess is that iterating over the memoryview is likely not more common than using getitem or slicing it. So, I feel like this optimization is not worth the (small) code complexity it adds and the change in semantics.

maurycy reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gabadie

gabadie commented Sep 15, 2026

Copy link
Copy Markdown

Hi! Granted I'm not familiar with python's policies for cherrypicking fixes for release, I was wondering whether you expected this fix in a specific future python 3.{14,15,16}.x release? Many thanks!

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@hugovk hugovk mentioned this pull request Sep 16, 2026
CPython 3.13-3.15 concurrency and memory-safety issue reports #157601
Open
@nascheme
Merge remote-tracking branch 'origin/main' into pythongh-127716-memor…
25f396f
…yview-thread-safe
@bedevere-app bedevere-app Bot added the type-feature A feature request or enhancement label Oct 3, 2026
ngoldbaum
ngoldbaum reviewed Oct 19, 2026
View reviewed changes

@ngoldbaum ngoldbaum left a comment

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm looking at alternate approaches that have better scaling for reads on shared memoryviews but Claude is still chewing on it. For now I found some minor issues with the current version of the PR, see below.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

maurycy reacted with heart emoji
All reactions
  • ❤️ 1 reaction
Comment thread Objects/memoryobject.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Doc/builtins/threadsafety.rst Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Misc/NEWS.d/next/Core_and_Builtins/2026-08-21-14-37-50.gh-issue-127716.KrPth8.rst Resolved

Uh oh!

There was an error while loading. Please reload this page.

@ngoldbaum

ngoldbaum commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

I think there is a version of this PR that uses hazard pointers and scales better under shared reads. However, it would be more complicated and I think it's possible to move to that approach in the future, in a manner than composes nicely with this. So I think we shouldn't delay this on a version that scales better: it can always be done in the future if reports come in about multithreaded performance. Or perhaps later in the 3.16 release cycle.

nascheme reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

nascheme and others added 2 commits October 20, 2026 17:01
@nascheme @ngoldbaum
Handle GC-cleared memoryviews before pinning
2668057
Incorporate review suggestions. Add a regression test using a tp_clear
helper, recommend per-thread views for read-heavy workloads, and clarify
the scope of the thread-safety NEWS entry.

Co-authored-by: Nathan Goldbaum <[email protected]>
@nascheme
Merge remote-tracking branch 'origin/main' into pythongh-127716-memor…
b9b2ad1
…yview-thread-safe
@gabadie gabadie mentioned this pull request Oct 21, 2026
python free threading support? YoSTEALTH/Liburing#33
Open
@ngoldbaum ngoldbaum mentioned this pull request Oct 21, 2026
Use-after-free in memoryview comparison via re-entrant struct.Struct.unpack_from #142663
Open
ngoldbaum and others added 6 commits October 21, 2026 13:31
@ngoldbaum
Prevent memoryview release during active operations
a5163e5
@ngoldbaum
merge new tests with existing tests
e0b6fa5
@ngoldbaum
Pin the managed buffer, not the view, during memoryview operations
e8a9aa5
@ngoldbaum
Don't clear memoryview iterator's reference on exhaustion in free-thr…
b68565c
…eaded build
@ngoldbaum
Restore memoryview pin helper signatures and release exhausted iterators
e84805c
@nascheme
Merge remote-tracking branch 'origin/main' into pythongh-127716-memor…
21b16d4
…yview-thread-safe
kumaraditya303
kumaraditya303 reviewed Oct 31, 2026
View reviewed changes
Comment thread Objects/memoryobject.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

kumaraditya303 added 2 commits October 31, 2026 18:40
@kumaraditya303
Merge branch 'main' into pythongh-127716-memoryview-thread-safe
de2ddbd
@kumaraditya303
Update Objects/memoryobject.c
8833436
@kumaraditya303 kumaraditya303 added the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Oct 31, 2026
@bedevere-bot

bedevere-bot commented Oct 31, 2026

Copy link
Copy Markdown

🤖 New build scheduled with the buildbot fleet by @kumaraditya303 for commit 8833436 🤖

Results will be shown at:

https://buildbot.python.org/all/#/grid?branch=refs%2Fpull%2F156248%2Fmerge

If you want to schedule another build, you need to add the 🔨 test-with-buildbots label again.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-bot bedevere-bot removed the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Oct 31, 2026
kumaraditya303
kumaraditya303 approved these changes Nov 5, 2026
View reviewed changes
@bedevere-app bedevere-app Bot added awaiting merge and removed awaiting core review labels Nov 5, 2026
@kumaraditya303
kumaraditya303 merged commit 7c5231e into python:main Nov 5, 2026
144 of 150 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot removed the awaiting merge label Nov 5, 2026
@gi0baro gi0baro mentioned this pull request Nov 9, 2026
Revert the output pump's memoryview workaround once CPython ships the gh-127716 fix gi0baro/pidrei#14
Open
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@ngoldbaum ngoldbaum ngoldbaum left review comments
@kumaraditya303 kumaraditya303 kumaraditya303 approved these changes

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

topic-free-threading type-feature A feature request or enhancement

Projects

None yet

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.

7 participants

@nascheme @weixlu @maurycy @gabadie @ngoldbaum @bedevere-bot @kumaraditya303
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.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.