| :mod:`!multiprocessing.shared_memory` --- Shared memory for direct access across processes | |
| ========================================================================================== | |
| .. module:: multiprocessing.shared_memory | |
| :synopsis: Provides shared memory for direct access across processes. | |
| **Source code:** :source:`Lib/multiprocessing/shared_memory.py` | |
| .. versionadded:: 3.8 | |
| .. index:: | |
| single: Shared Memory | |
| single: POSIX Shared Memory | |
| single: Named Shared Memory | |
| -------------- | |
| This module provides a class, :class:`SharedMemory`, for the allocation | |
| and management of shared memory to be accessed by one or more processes | |
| on a multicore or symmetric multiprocessor (SMP) machine. To assist with | |
| the life-cycle management of shared memory especially across distinct | |
| processes, a :class:`~multiprocessing.managers.BaseManager` subclass, | |
| :class:`~multiprocessing.managers.SharedMemoryManager`, is also provided in the | |
| :mod:`multiprocessing.managers` module. | |
| In this module, shared memory refers to "POSIX style" shared memory blocks | |
| (though is not necessarily implemented explicitly as such) and does not refer | |
| to "distributed shared memory". This style of shared memory permits distinct | |
| processes to potentially read and write to a common (or shared) region of | |
| volatile memory. Processes are conventionally limited to only have access to | |
| their own process memory space but shared memory permits the sharing | |
| of data between processes, avoiding the need to instead send messages between | |
| processes containing that data. Sharing data directly via memory can provide | |
| significant performance benefits compared to sharing data via disk or socket | |
| or other communications requiring the serialization/deserialization and | |
| copying of data. | |
| .. class:: SharedMemory(name=None, create=False, size=0, *, track=True) | |
| Create an instance of the :class:`!SharedMemory` class for either | |
| creating a new shared memory block or attaching to an existing shared | |
| memory block. Each shared memory block is assigned a unique name. | |
| In this way, one process can create a shared memory block with a | |
| particular name and a different process can attach to that same shared | |
| memory block using that same name. | |
| As a resource for sharing data across processes, shared memory blocks | |
| may outlive the original process that created them. When one process | |
| no longer needs access to a shared memory block that might still be | |
| needed by other processes, the :meth:`close` method should be called. | |
| When a shared memory block is no longer needed by any process, the | |
| :meth:`unlink` method should be called to ensure proper cleanup. | |
| :param name: | |
| The unique name for the requested shared memory, specified as a string. | |
| When creating a new shared memory block, if ``None`` (the default) | |
| is supplied for the name, a novel name will be generated. | |
| :type name: str | None | |
| :param bool create: | |
| Control whether a new shared memory block is created (``True``) | |
| or an existing shared memory block is attached (``False``). | |
| :param int size: | |
| The requested number of bytes when creating a new shared memory block. | |
| Because some platforms choose to allocate chunks of memory | |
| based upon that platform's memory page size, the exact size of the shared | |
| memory block may be larger or equal to the size requested. | |
| When attaching to an existing shared memory block, | |
| the *size* parameter is ignored. | |
| :param bool track: | |
| When ``True``, register the shared memory block with a resource | |
| tracker process on platforms where the OS does not do this automatically. | |
| The resource tracker ensures proper cleanup of the shared memory even | |
| if all other processes with access to the memory exit without doing so. | |
| Python processes created from a common ancestor using :mod:`multiprocessing` | |
| facilities share a single resource tracker process, and the lifetime of | |
| shared memory segments is handled automatically among these processes. | |
| Python processes created in any other way will receive their own | |
| resource tracker when accessing shared memory with *track* enabled. | |
| This will cause the shared memory to be deleted by the resource tracker | |
| of the first process that terminates. | |
| To avoid this issue, users of :mod:`subprocess` or standalone Python | |
| processes should set *track* to ``False`` when there is already another | |
| process in place that does the bookkeeping. | |
| *track* is ignored on Windows, which has its own tracking and | |
| automatically deletes shared memory when all handles to it have been closed. | |
| .. versionchanged:: 3.13 | |
| Added the *track* parameter. | |
| .. method:: close() | |
| Close the file descriptor/handle to the shared memory from this | |
| instance. :meth:`close` should be called once access to the shared | |
| memory block from this instance is no longer needed. Depending | |
| on operating system, the underlying memory may or may not be freed | |
| even if all handles to it have been closed. To ensure proper cleanup, | |
| use the :meth:`unlink` method. | |
| .. method:: unlink() | |
| Delete the underlying shared memory block. This should be called only | |
| once per shared memory block regardless of the number of handles to it, | |
| even in other processes. | |
| :meth:`unlink` and :meth:`close` can be called in any order, but | |
| trying to access data inside a shared memory block after :meth:`unlink` | |
| may result in memory access errors, depending on platform. | |
| This method has no effect on Windows, where the only way to delete a | |
| shared memory block is to close all handles. | |
| .. attribute:: buf | |
| A memoryview of contents of the shared memory block. | |
| .. attribute:: name | |
| Read-only access to the unique name of the shared memory block. | |
| .. attribute:: size | |
| Read-only access to size in bytes of the shared memory block. | |
| The following example demonstrates low-level use of :class:`SharedMemory` | |
| instances:: | |
| >>> from multiprocessing import shared_memory | |
| >>> shm_a = shared_memory.SharedMemory(create=True, size=10) | |
| >>> type(shm_a.buf) | |
| <class 'memoryview'> | |
| >>> buffer = shm_a.buf | |
| >>> len(buffer) | |
| 10 | |
| >>> buffer[:4] = bytearray([22, 33, 44, 55]) # Modify multiple at once | |
| >>> buffer[4] = 100 # Modify single byte at a time | |
| >>> # Attach to an existing shared memory block | |
| >>> shm_b = shared_memory.SharedMemory(shm_a.name) | |
| >>> import array | |
| >>> array.array('b', shm_b.buf[:5]) # Copy the data into a new array.array | |
| array('b', [22, 33, 44, 55, 100]) | |
| >>> shm_b.buf[:5] = b'howdy' # Modify via shm_b using bytes | |
| >>> bytes(shm_a.buf[:5]) # Access via shm_a | |
| b'howdy' | |
| >>> shm_b.close() # Close each SharedMemory instance | |
| >>> shm_a.close() | |
| >>> shm_a.unlink() # Call unlink only once to release the shared memory | |
| The following example demonstrates a practical use of the :class:`SharedMemory` | |
| class with `NumPy arrays <https://numpy.org/>`_, accessing the | |
| same :class:`!numpy.ndarray` from two distinct Python shells: | |
| .. doctest:: | |
| :options: +SKIP | |
| >>> # In the first Python interactive shell | |
| >>> import numpy as np | |
| >>> a = np.array([1, 1, 2, 3, 5, 8]) # Start with an existing NumPy array | |
| >>> from multiprocessing import shared_memory | |
| >>> shm = shared_memory.SharedMemory(create=True, size=a.nbytes) | |
| >>> # Now create a NumPy array backed by shared memory | |
| >>> b = np.ndarray(a.shape, dtype=a.dtype, buffer=shm.buf) | |
| >>> b[:] = a[:] # Copy the original data into shared memory | |
| >>> b | |
| array([1, 1, 2, 3, 5, 8]) | |
| >>> type(b) | |
| <class 'numpy.ndarray'> | |
| >>> type(a) | |
| <class 'numpy.ndarray'> | |
| >>> shm.name # We did not specify a name so one was chosen for us | |
| 'psm_21467_46075' | |
| >>> # In either the same shell or a new Python shell on the same machine | |
| >>> import numpy as np | |
| >>> from multiprocessing import shared_memory | |
| >>> # Attach to the existing shared memory block | |
| >>> existing_shm = shared_memory.SharedMemory(name='psm_21467_46075') | |
| >>> # Note that a.shape is (6,) and a.dtype is np.int64 in this example | |
| >>> c = np.ndarray((6,), dtype=np.int64, buffer=existing_shm.buf) | |
| >>> c | |
| array([1, 1, 2, 3, 5, 8]) | |
| >>> c[-1] = 888 | |
| >>> c | |
| array([ 1, 1, 2, 3, 5, 888]) | |
| >>> # Back in the first Python interactive shell, b reflects this change | |
| >>> b | |
| array([ 1, 1, 2, 3, 5, 888]) | |
| >>> # Clean up from within the second Python shell | |
| >>> del c # Unnecessary; merely emphasizing the array is no longer used | |
| >>> existing_shm.close() | |
| >>> # Clean up from within the first Python shell | |
| >>> del b # Unnecessary; merely emphasizing the array is no longer used | |
| >>> shm.close() | |
| >>> shm.unlink() # Free and release the shared memory block at the very end | |
| .. class:: SharedMemoryManager([address[, authkey]]) | |
| :module: multiprocessing.managers | |
| A subclass of :class:`multiprocessing.managers.BaseManager` which can be | |
| used for the management of shared memory blocks across processes. | |
| A call to :meth:`~multiprocessing.managers.BaseManager.start` on a | |
| :class:`!SharedMemoryManager` instance causes a new process to be started. | |
| This new process's sole purpose is to manage the life cycle | |
| of all shared memory blocks created through it. To trigger the release | |
| of all shared memory blocks managed by that process, call | |
| :meth:`~multiprocessing.managers.BaseManager.shutdown` on the instance. | |
| This triggers a :meth:`~multiprocessing.shared_memory.SharedMemory.unlink` call | |
| on all of the :class:`SharedMemory` objects managed by that process and then | |
| stops the process itself. By creating :class:`!SharedMemory` instances | |
| through a :class:`!SharedMemoryManager`, we avoid the need to manually track | |
| and trigger the freeing of shared memory resources. | |
| This class provides methods for creating and returning :class:`SharedMemory` | |
| instances and for creating a list-like object (:class:`ShareableList`) | |
| backed by shared memory. | |
| Refer to :class:`~multiprocessing.managers.BaseManager` for a description | |
| of the inherited *address* and *authkey* optional input arguments and how | |
| they may be used to connect to an existing :class:`!SharedMemoryManager` service | |
| from other processes. | |
| .. method:: SharedMemory(size) | |
| Create and return a new :class:`SharedMemory` object with the | |
| specified *size* in bytes. | |
| .. method:: ShareableList(sequence) | |
| Create and return a new :class:`ShareableList` object, initialized | |
| by the values from the input *sequence*. | |
| The following example demonstrates the basic mechanisms of a | |
| :class:`~multiprocessing.managers.SharedMemoryManager`: | |
| .. doctest:: | |
| :options: +SKIP | |
| >>> from multiprocessing.managers import SharedMemoryManager | |
| >>> smm = SharedMemoryManager() | |
| >>> smm.start() # Start the process that manages the shared memory blocks | |
| >>> sl = smm.ShareableList(range(4)) | |
| >>> sl | |
| ShareableList([0, 1, 2, 3], name='psm_6572_7512') | |
| >>> raw_shm = smm.SharedMemory(size=128) | |
| >>> another_sl = smm.ShareableList('alpha') | |
| >>> another_sl | |
| ShareableList(['a', 'l', 'p', 'h', 'a'], name='psm_6572_12221') | |
| >>> smm.shutdown() # Calls unlink() on sl, raw_shm, and another_sl | |
| The following example depicts a potentially more convenient pattern for using | |
| :class:`~multiprocessing.managers.SharedMemoryManager` objects via the | |
| :keyword:`with` statement to ensure that all shared memory blocks are released | |
| after they are no longer needed: | |
| .. doctest:: | |
| :options: +SKIP | |
| >>> with SharedMemoryManager() as smm: | |
| ... sl = smm.ShareableList(range(2000)) | |
| ... # Divide the work among two processes, storing partial results in sl | |
| ... p1 = Process(target=do_work, args=(sl, 0, 1000)) | |
| ... p2 = Process(target=do_work, args=(sl, 1000, 2000)) | |
| ... p1.start() | |
| ... p2.start() # A multiprocessing.Pool might be more efficient | |
| ... p1.join() | |
| ... p2.join() # Wait for all work to complete in both processes | |
| ... total_result = sum(sl) # Consolidate the partial results now in sl | |
| When using a :class:`~multiprocessing.managers.SharedMemoryManager` | |
| in a :keyword:`with` statement, the shared memory blocks created using that | |
| manager are all released when the :keyword:`!with` statement's code block | |
| finishes execution. | |
| .. class:: ShareableList(sequence=None, *, name=None) | |
| Provide a mutable list-like object where all values stored within are | |
| stored in a shared memory block. | |
| This constrains storable values to the following built-in data types: | |
| * :class:`int` (signed 64-bit) | |
| * :class:`float` | |
| * :class:`bool` | |
| * :class:`str` (less than 10M bytes each when encoded as UTF-8) | |
| * :class:`bytes` (less than 10M bytes each) | |
| * ``None`` | |
| It also notably differs from the built-in :class:`list` type | |
| in that these lists can not change their overall length | |
| (i.e. no :meth:`!append`, :meth:`!insert`, etc.) and do not | |
| support the dynamic creation of new :class:`!ShareableList` instances | |
| via slicing. | |
| *sequence* is used in populating a new :class:`!ShareableList` full of values. | |
| Set to ``None`` to instead attach to an already existing | |
| :class:`!ShareableList` by its unique shared memory name. | |
| *name* is the unique name for the requested shared memory, as described | |
| in the definition for :class:`SharedMemory`. When attaching to an | |
| existing :class:`!ShareableList`, specify its shared memory block's unique | |
| name while leaving *sequence* set to ``None``. | |
| .. note:: | |
| A known issue exists for :class:`bytes` and :class:`str` values. | |
| If they end with ``\x00`` nul bytes or characters, those may be | |
| *silently stripped* when fetching them by index from the | |
| :class:`!ShareableList`. This ``.rstrip(b'\x00')`` behavior is | |
| considered a bug and may go away in the future. See :gh:`106939`. | |
| For applications where rstripping of trailing nulls is a problem, | |
| work around it by always unconditionally appending an extra non-0 | |
| byte to the end of such values when storing and unconditionally | |
| removing it when fetching: | |
| .. doctest:: | |
| >>> from multiprocessing import shared_memory | |
| >>> nul_bug_demo = shared_memory.ShareableList(['?\x00', b'\x03\x02\x01\x00\x00\x00']) | |
| >>> nul_bug_demo[0] | |
| '?' | |
| >>> nul_bug_demo[1] | |
| b'\x03\x02\x01' | |
| >>> nul_bug_demo.shm.unlink() | |
| >>> padded = shared_memory.ShareableList(['?\x00\x07', b'\x03\x02\x01\x00\x00\x00\x07']) | |
| >>> padded[0][:-1] | |
| '?\x00' | |
| >>> padded[1][:-1] | |
| b'\x03\x02\x01\x00\x00\x00' | |
| >>> padded.shm.unlink() | |
| .. method:: count(value) | |
| Return the number of occurrences of *value*. | |
| .. method:: index(value) | |
| Return first index position of *value*. | |
| Raise :exc:`ValueError` if *value* is not present. | |
| .. attribute:: format | |
| Read-only attribute containing the :mod:`struct` packing format used by | |
| all currently stored values. | |
| .. attribute:: shm | |
| The :class:`SharedMemory` instance where the values are stored. | |
| The following example demonstrates basic use of a :class:`ShareableList` | |
| instance: | |
| >>> from multiprocessing import shared_memory | |
| >>> a = shared_memory.ShareableList(['howdy', b'HoWdY', -273.154, 100, None, True, 42]) | |
| >>> [ type(entry) for entry in a ] | |
| [<class 'str'>, <class 'bytes'>, <class 'float'>, <class 'int'>, <class 'NoneType'>, <class 'bool'>, <class 'int'>] | |
| >>> a[2] | |
| -273.154 | |
| >>> a[2] = -78.5 | |
| >>> a[2] | |
| -78.5 | |
| >>> a[2] = 'dry ice' # Changing data types is supported as well | |
| >>> a[2] | |
| 'dry ice' | |
| >>> a[2] = 'larger than previously allocated storage space' | |
| Traceback (most recent call last): | |
| ... | |
| ValueError: exceeds available storage for existing str | |
| >>> a[2] | |
| 'dry ice' | |
| >>> len(a) | |
| 7 | |
| >>> a.index(42) | |
| 6 | |
| >>> a.count(b'howdy') | |
| 0 | |
| >>> a.count(b'HoWdY') | |
| 1 | |
| >>> a.shm.close() | |
| >>> a.shm.unlink() | |
| >>> del a # Use of a ShareableList after call to unlink() is unsupported | |
| The following example depicts how one, two, or many processes may access the | |
| same :class:`ShareableList` by supplying the name of the shared memory block | |
| behind it: | |
| >>> b = shared_memory.ShareableList(range(5)) # In a first process | |
| >>> c = shared_memory.ShareableList(name=b.shm.name) # In a second process | |
| >>> c | |
| ShareableList([0, 1, 2, 3, 4], name='...') | |
| >>> c[-1] = -999 | |
| >>> b[-1] | |
| -999 | |
| >>> b.shm.close() | |
| >>> c.shm.close() | |
| >>> c.shm.unlink() | |
| The following examples demonstrates that :class:`ShareableList` | |
| (and underlying :class:`SharedMemory`) objects | |
| can be pickled and unpickled if needed. | |
| Note, that it will still be the same shared object. | |
| This happens, because the deserialized object has | |
| the same unique name and is just attached to an existing | |
| object with the same name (if the object is still alive): | |
| >>> import pickle | |
| >>> from multiprocessing import shared_memory | |
| >>> sl = shared_memory.ShareableList(range(10)) | |
| >>> list(sl) | |
| [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] | |
| >>> deserialized_sl = pickle.loads(pickle.dumps(sl)) | |
| >>> list(deserialized_sl) | |
| [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] | |
| >>> sl[0] = -1 | |
| >>> deserialized_sl[1] = -2 | |
| >>> list(sl) | |
| [-1, -2, 2, 3, 4, 5, 6, 7, 8, 9] | |
| >>> list(deserialized_sl) | |
| [-1, -2, 2, 3, 4, 5, 6, 7, 8, 9] | |
| >>> sl.shm.close() | |
| >>> sl.shm.unlink() |