mirror of
https://github.com/python/cpython.git
synced 2026-04-14 07:41:00 +00:00
[3.14] gh-142518: Add thread safety notes for the buffer protocol (GH-145911) (#146106)
(cherry picked from commit 847f83ef1c)
Co-authored-by: Lysandros Nikolaou <lisandrosnik@gmail.com>
This commit is contained in:
parent
c9900734fa
commit
f20a637644
4 changed files with 100 additions and 0 deletions
|
|
@ -3055,6 +3055,24 @@ Buffer Object Structures
|
|||
|
||||
(5) Return ``0``.
|
||||
|
||||
**Thread safety:**
|
||||
|
||||
In the :term:`free-threaded build`, implementations must ensure:
|
||||
|
||||
* The export counter increment in step (3) is atomic.
|
||||
|
||||
* The underlying buffer data remains valid and at a stable memory
|
||||
location for the lifetime of all exports.
|
||||
|
||||
* For objects that support resizing or reallocation (such as
|
||||
:class:`bytearray`), the export counter is checked atomically before
|
||||
such operations, and :exc:`BufferError` is raised if exports exist.
|
||||
|
||||
* The function is safe to call concurrently from multiple threads.
|
||||
|
||||
See also :ref:`thread-safety-memoryview` for the Python-level
|
||||
thread safety guarantees of :class:`memoryview` objects.
|
||||
|
||||
If *exporter* is part of a chain or tree of buffer providers, two main
|
||||
schemes can be used:
|
||||
|
||||
|
|
@ -3100,6 +3118,16 @@ Buffer Object Structures
|
|||
|
||||
(2) If the counter is ``0``, free all memory associated with *view*.
|
||||
|
||||
**Thread safety:**
|
||||
|
||||
In the :term:`free-threaded build`:
|
||||
|
||||
* The export counter decrement in step (1) must be atomic.
|
||||
|
||||
* Resource cleanup when the counter reaches zero must be done atomically,
|
||||
as the final release may race with concurrent releases from other
|
||||
threads and dellocation must only happen once.
|
||||
|
||||
The exporter MUST use the :c:member:`~Py_buffer.internal` field to keep
|
||||
track of buffer-specific resources. This field is guaranteed to remain
|
||||
constant, while a consumer MAY pass a copy of the original buffer as the
|
||||
|
|
|
|||
|
|
@ -5009,6 +5009,9 @@ copying.
|
|||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
For information on the thread safety of :class:`memoryview` objects in
|
||||
the :term:`free-threaded build`, see :ref:`thread-safety-memoryview`.
|
||||
|
||||
|
||||
.. _types-set:
|
||||
|
||||
|
|
|
|||
|
|
@ -548,3 +548,59 @@ Thread safety for bytearray objects
|
|||
|
||||
Consider external synchronization when sharing :class:`bytearray` instances
|
||||
across threads. See :ref:`freethreading-python-howto` for more information.
|
||||
|
||||
|
||||
.. _thread-safety-memoryview:
|
||||
|
||||
Thread safety for memoryview objects
|
||||
====================================
|
||||
|
||||
:class:`memoryview` objects provide access to the internal data of an
|
||||
underlying object without copying. Thread safety depends on both the
|
||||
memoryview itself and the underlying buffer exporter.
|
||||
|
||||
The memoryview implementation uses atomic operations to track its own
|
||||
exports in the :term:`free-threaded build`. Creating and
|
||||
releasing a memoryview are thread-safe. Attribute access (e.g.,
|
||||
:attr:`~memoryview.shape`, :attr:`~memoryview.format`) reads fields that
|
||||
are immutable for the lifetime of the memoryview, so concurrent reads
|
||||
are safe as long as the memoryview has not been released.
|
||||
|
||||
However, the actual data accessed through the memoryview is owned by the
|
||||
underlying object. Concurrent access to this data is only safe if the
|
||||
underlying object supports it:
|
||||
|
||||
* For immutable objects like :class:`bytes`, concurrent reads through
|
||||
multiple memoryviews are safe.
|
||||
|
||||
* For mutable objects like :class:`bytearray`, reading and writing the
|
||||
same memory region from multiple threads without external
|
||||
synchronization is not safe and may result in data corruption.
|
||||
Note that even read-only memoryviews of mutable objects do not
|
||||
prevent data races if the underlying object is modified from
|
||||
another thread.
|
||||
|
||||
.. code-block::
|
||||
:class: bad
|
||||
|
||||
# NOT safe: concurrent writes to the same buffer
|
||||
data = bytearray(1000)
|
||||
view = memoryview(data)
|
||||
# Thread 1: view[0:500] = b'x' * 500
|
||||
# Thread 2: view[0:500] = b'y' * 500
|
||||
|
||||
.. code-block::
|
||||
:class: good
|
||||
|
||||
# Safe: use a lock for concurrent access
|
||||
import threading
|
||||
lock = threading.Lock()
|
||||
data = bytearray(1000)
|
||||
view = memoryview(data)
|
||||
|
||||
with lock:
|
||||
view[0:500] = b'x' * 500
|
||||
|
||||
Resizing or reallocating the underlying object (such as calling
|
||||
:meth:`bytearray.resize`) while a memoryview is exported raises
|
||||
:exc:`BufferError`. This is enforced regardless of threading.
|
||||
|
|
|
|||
|
|
@ -3640,12 +3640,25 @@ implement the protocol in Python.
|
|||
provides a convenient way to interpret the flags. The method must return
|
||||
a :class:`memoryview` object.
|
||||
|
||||
**Thread safety:** In :term:`free-threaded <free threading>` Python,
|
||||
implementations must manage any internal export counter using atomic
|
||||
operations. The method must be safe to call concurrently from multiple
|
||||
threads, and the returned buffer's underlying data must remain valid
|
||||
until the corresponding :meth:`~object.__release_buffer__` call
|
||||
completes. See :ref:`thread-safety-memoryview` for details.
|
||||
|
||||
.. method:: object.__release_buffer__(self, buffer)
|
||||
|
||||
Called when a buffer is no longer needed. The *buffer* argument is a
|
||||
:class:`memoryview` object that was previously returned by
|
||||
:meth:`~object.__buffer__`. The method must release any resources associated
|
||||
with the buffer. This method should return ``None``.
|
||||
|
||||
**Thread safety:** In :term:`free-threaded <free threading>` Python,
|
||||
any export counter decrement must use atomic operations. Resource
|
||||
cleanup must be thread-safe, as the final release may race with
|
||||
concurrent releases from other threads.
|
||||
|
||||
Buffer objects that do not need to perform any cleanup are not required
|
||||
to implement this method.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue