Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions Doc/library/array.rst
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,63 @@ Examples::
array('d', [1.0, 2.0, 3.14, -inf, nan])


.. _array-thread-safety:

Thread safety
-------------

In the :term:`free-threaded build`, array operations are safe to call from
multiple threads and will not corrupt the array. Most operations hold the
:term:`per-object lock`. Operations that take a second array, such as
:meth:`~array.array.extend`, concatenation, comparison and slice assignment,
lock both arrays.

The :func:`len` function is :term:`lock-free` and
:term:`atomic <atomic operation>`.

The following operations are also :term:`lock-free`. They do not block
concurrent modifications and may observe intermediate states from
operations that hold the :term:`per-object lock`:

.. code-block::
:class: maybe

a[i] # read a single item
a[i] = x # write a single item
for x in a: # iteration
...

These operations have the following limitations:

* Reading a single item uses an :term:`atomic <atomic operation>` read,
except for type code ``'Zd'``. The read may observe intermediate states
from concurrent modifications, such as :meth:`~array.array.reverse` or
:meth:`~array.array.insert`.

* Writing a single item is not :term:`atomic <atomic operation>` with
respect to concurrent operations that resize or rearrange the array
(for example :meth:`~array.array.append`, :meth:`~array.array.extend`,
:meth:`~array.array.insert`, :meth:`~array.array.pop`,
:meth:`~array.array.reverse` or slice assignment). The write can be
lost: ``a[i] = x`` returns normally but the array may keep the previous
item. Writes are not lost if the other threads only read the array or
write single items.

* Reading or writing an item of type code ``'Zd'`` is not
:term:`atomic <atomic operation>`. The real and imaginary parts are
accessed separately, so a concurrent read and write of the same item
may return the real part of one value and the imaginary part of the other.

Operations that involve multiple accesses, as well as iteration, are not
:term:`atomic <atomic operation>`. Use external synchronization, such as a
:class:`threading.Lock`, when these operations must be atomic or to avoid
the limitations above.

Objects that use the buffer interface of an array, such as
:class:`memoryview`, access its memory without holding the array's
:term:`per-object lock`. Concurrent reads and writes to the same memory
region through the buffer interface require external synchronization.

.. seealso::

Module :mod:`struct`
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_pymem.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ extern wchar_t *_PyMem_DefaultRawWcsdup(const wchar_t *str);
extern int _PyMem_DebugEnabled(void);

// Enqueue a pointer to be freed possibly after some delay.
extern void _PyMem_FreeDelayed(void *ptr, size_t size);
PyAPI_FUNC(void) _PyMem_FreeDelayed(void *ptr, size_t size);

// Periodically process delayed free requests.
extern void _PyMem_ProcessDelayed(PyThreadState *tstate);
Expand Down
39 changes: 39 additions & 0 deletions Lib/test/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -1272,6 +1272,29 @@ def test_obsolete_write_lock(self):
a = array.array('B', b"")
self.assertRaises(BufferError, _testcapi.getbuffer_with_null_view, a)

@support.cpython_only
def test_sequence_delitem(self):
# PySequence_DelItem() goes through the sq_ass_item slot.
_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
delitem = _testlimitedcapi.sequence_delitem
a = array.array(self.typecode, self.example)
delitem(a, 0)
self.assertEqual(a, array.array(self.typecode, self.example[1:]))
self.assertRaises(IndexError, delitem, a, len(a))
self.assertRaises(IndexError, delitem, a, -len(a) - 1)
self.assertEqual(a, array.array(self.typecode, self.example[1:]))

def test_create_from_shared_iterator(self):
# The iterator returned by __iter__() can have other references.
class Iterable:
def __init__(self, it):
self.it = it
def __iter__(self):
return self.it

a = array.array(self.typecode, Iterable(iter(self.example)))
self.assertEqual(a, array.array(self.typecode, self.example))

def test_free_after_iterating(self):
support.check_free_after_iterating(self, iter, array.array,
(self.typecode,))
Expand Down Expand Up @@ -1316,6 +1339,22 @@ def test_unicode(self):

self.assertRaises(TypeError, a.fromunicode)

def test_invalid_code_point(self):
# An item that is not a valid code point raises ValueError, also
# for the following items.
a = array.array(self.typecode, 'ab')
a.frombytes((0x110000).to_bytes(a.itemsize, sys.byteorder))
a.fromunicode('c')
self.assertRaises(ValueError, a.__getitem__, 2)
self.assertRaises(ValueError, a.__getitem__, -2)
self.assertRaises(ValueError, a.tolist)
it = iter(a)
self.assertEqual(next(it), 'a')
self.assertEqual(next(it), 'b')
self.assertRaises(ValueError, next, it)
self.assertEqual(next(it), 'c')
self.assertRaises(StopIteration, next, it)


class NumberTest(BaseTest):

Expand Down
Loading
Loading