8000 gh-128182: Add per-object memory access synchronization to `ctypes` (… · python/cpython@8dfc743 · GitHub
[go: up one dir, main page]

Skip to content

Commit 8dfc743

Browse files
gh-128182: Add per-object memory access synchronization to ctypes (GH-128490)
1 parent 5044c22 commit 8dfc743

File tree

5 files changed

+234
-47
lines changed

5 files changed

+234
-47
lines changed

Doc/library/ctypes.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,36 @@ invalid non-\ ``NULL`` pointers would crash Python)::
870870
ValueError: NULL pointer access
871871
>>>
872872

873+
.. _ctypes-thread-safety:
874+
875+
Thread safety without the GIL
876+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
877+
878+
In Python 3.13, the :term:`GIL` may be disabled on :term:`experimental free threaded <free threading>` builds.
879+
In ctypes, reads and writes to a single object concurrently is safe, but not across multiple objects:
880+
881+
.. code-block:: pycon
882+
883+
>>> number = c_int(42)
884+
>>> pointer_a = pointer(number)
885+
>>> pointer_b = pointer(number)
886+
887+
In the above, it's only safe for one object to read and write to the address at once if the GIL is disabled.
888+
So, ``pointer_a`` can be shared and written to across multiple threads, but only if ``pointer_b``
889+
is not also attempting to do the same. If this is an issue, consider using a :class:`threading.Lock`
890+
to synchronize access to memory:
891+
892+
.. code-block:: pycon
893+
894+
>>> import threading
895+
>>> lock = threading.Lock()
896+
>>> # Thread 1
897+
>>> with lock:
898+
... pointer_a.contents = 24
899+
>>> # Thread 2
900+
>>> with lock:
901+
... pointer_b.contents = 42
902+
873903
874904
.. _ctypes-type-conversions:
875905

Lib/test/test_ctypes/test_arrays.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
create_string_buffer, create_unicode_buffer,
66
c_char, c_wchar, c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint,
77
c_long, c_ulonglong, c_float, c_double, c_longdouble)
8-
from test.support import bigmemtest, _2G
8+
from test.support import bigmemtest, _2G, threading_helper, Py_GIL_DISABLED
99
from ._support import (_CData, PyCArrayType, Py_TPFLAGS_DISALLOW_INSTANTIATION,
1010
Py_TPFLAGS_IMMUTABLETYPE)
1111

@@ -267,6 +267,26 @@ def test_bpo36504_signed_int_overflow(self):
267267
def test_large_array(self, size):
268268
c_char * size
269269

270+
@threading_helper.requires_working_threading()
271+
@unittest.skipUnless(Py_GIL_DISABLED, "only meaningful if the GIL is disabled")
272+
def test_thread_safety(self):
273+
from threading import Thread
274+
275+
buffer = (ctypes.c_char_p * 10)()
276+
277+
def run():
278+
for i in range(100):
279+
buffer.value = b"hello"
280+
buffer[0] = b"j"
281+
282+
with threading_helper.catch_threading_exception() as cm:
283+
threads = (Thread(target=run) for _ in range(25))
284+
with threading_helper.start_threads(threads):
285+
pass
286+
287+
if cm.exc_value:
288+
raise cm.exc_value
289+
270290

271291
if __name__ == '__main__':
272292
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix crash when using :mod:`ctypes` pointers concurrently on the :term:`free
2+
threaded <free threading>` build.

0 commit comments

Comments
 (0)
0