Skip to content

Commit 35bd2fd

Browse files
committed
gh-142518: Add thread safety notes for the buffer protocol
1 parent 7836ecc commit 35bd2fd

File tree

4 files changed

+100
-0
lines changed

4 files changed

+100
-0
lines changed

Doc/c-api/typeobj.rst

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3057,6 +3057,24 @@ Buffer Object Structures
30573057

30583058
(5) Return ``0``.
30593059

3060+
**Thread safety:**
3061+
3062+
In the :term:`free-threaded build`, implementations must ensure:
3063+
3064+
* The export counter increment in step (3) is atomic.
3065+
3066+
* The underlying buffer data remains valid and at a stable memory
3067+
location for the lifetime of all exports.
3068+
3069+
* For objects that support resizing or reallocation (such as
3070+
:class:`bytearray`), the export counter is checked atomically before
3071+
such operations, and :exc:`BufferError` is raised if exports exist.
3072+
3073+
* The function is safe to call concurrently from multiple threads.
3074+
3075+
See also :ref:`thread-safety-memoryview` for the Python-level
3076+
thread safety guarantees of :class:`memoryview` objects.
3077+
30603078
If *exporter* is part of a chain or tree of buffer providers, two main
30613079
schemes can be used:
30623080

@@ -3102,6 +3120,16 @@ Buffer Object Structures
31023120

31033121
(2) If the counter is ``0``, free all memory associated with *view*.
31043122

3123+
**Thread safety:**
3124+
3125+
In the :term:`free-threaded build`:
3126+
3127+
* The export counter decrement in step (1) must be atomic.
3128+
3129+
* Resource cleanup when the counter reaches zero must be reentrant,
3130+
as the final release may race with concurrent releases from other
3131+
threads.
3132+
31053133
The exporter MUST use the :c:member:`~Py_buffer.internal` field to keep
31063134
track of buffer-specific resources. This field is guaranteed to remain
31073135
constant, while a consumer MAY pass a copy of the original buffer as the

Doc/library/stdtypes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5040,6 +5040,9 @@ copying.
50405040

50415041
.. versionadded:: 3.3
50425042

5043+
For information on the thread safety of :class:`memoryview` objects in
5044+
the :term:`free-threaded build`, see :ref:`thread-safety-memoryview`.
5045+
50435046

50445047
.. _types-set:
50455048

Doc/library/threadsafety.rst

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,3 +342,59 @@ thread, iterate over a copy:
342342
343343
Consider external synchronization when sharing :class:`dict` instances
344344
across threads.
345+
346+
347+
.. _thread-safety-memoryview:
348+
349+
Thread safety for memoryview objects
350+
====================================
351+
352+
:class:`memoryview` objects provide access to the internal data of an
353+
underlying object without copying. Thread safety depends on both the
354+
memoryview itself and the underlying buffer exporter.
355+
356+
The memoryview implementation uses atomic operations to track its own
357+
exports in the :term:`free-threaded build`. Creating and
358+
releasing a memoryview are thread-safe. Attribute access (e.g.,
359+
:attr:`~memoryview.shape`, :attr:`~memoryview.format`) reads fields that
360+
are immutable for the lifetime of the memoryview, so concurrent reads
361+
are safe as long as the memoryview has not been released.
362+
363+
However, the actual data accessed through the memoryview is owned by the
364+
underlying object. Concurrent access to this data is only safe if the
365+
underlying object supports it:
366+
367+
* For immutable objects like :class:`bytes`, concurrent reads through
368+
multiple memoryviews are safe.
369+
370+
* For mutable objects like :class:`bytearray`, reading and writing the
371+
same memory region from multiple threads without external
372+
synchronization is not safe and may result in data corruption.
373+
Note that even read-only memoryviews of mutable objects do not
374+
prevent data races if the underlying object is modified from
375+
another thread.
376+
377+
.. code-block::
378+
:class: bad
379+
380+
# NOT safe: concurrent writes to the same buffer
381+
data = bytearray(1000)
382+
view = memoryview(data)
383+
# Thread 1: view[0:500] = b'x' * 500
384+
# Thread 2: view[0:500] = b'y' * 500
385+
386+
.. code-block::
387+
:class: good
388+
389+
# Safe: use a lock for concurrent access
390+
import threading
391+
lock = threading.Lock()
392+
data = bytearray(1000)
393+
view = memoryview(data)
394+
395+
with lock:
396+
view[0:500] = b'x' * 500
397+
398+
Resizing or reallocating the underlying object (such as calling
399+
:meth:`bytearray.resize`) while a memoryview is exported raises
400+
:exc:`BufferError`. This is enforced regardless of threading.

Doc/reference/datamodel.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3637,12 +3637,25 @@ implement the protocol in Python.
36373637
provides a convenient way to interpret the flags. The method must return
36383638
a :class:`memoryview` object.
36393639

3640+
**Thread safety:** In :term:`free-threaded <free threading>` Python,
3641+
implementations must manage any internal export counter using atomic
3642+
operations. The method must be safe to call concurrently from multiple
3643+
threads, and the returned buffer's underlying data must remain valid
3644+
until the corresponding :meth:`~object.__release_buffer__` call
3645+
completes. See :ref:`thread-safety-memoryview` for details.
3646+
36403647
.. method:: object.__release_buffer__(self, buffer)
36413648

36423649
Called when a buffer is no longer needed. The *buffer* argument is a
36433650
:class:`memoryview` object that was previously returned by
36443651
:meth:`~object.__buffer__`. The method must release any resources associated
36453652
with the buffer. This method should return ``None``.
3653+
3654+
**Thread safety:** In :term:`free-threaded <free threading>` Python,
3655+
any export counter decrement must use atomic operations. Resource
3656+
cleanup must be thread-safe, as the final release may race with
3657+
concurrent releases from other threads.
3658+
36463659
Buffer objects that do not need to perform any cleanup are not required
36473660
to implement this method.
36483661

0 commit comments

Comments
 (0)