mirror of
				https://github.com/python/cpython.git
				synced 2025-10-27 19:54:38 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			371 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
			
		
		
	
	
			371 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
| :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:`SharedMemoryManager`, is also provided in the
 | |
| ``multiprocessing.managers`` module.
 | |
| 
 | |
| In this module, shared memory refers to "System V 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)
 | |
| 
 | |
|    Creates a new shared memory block or attaches 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.
 | |
| 
 | |
|    *name* is 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.
 | |
| 
 | |
|    *create* controls whether a new shared memory block is created (``True``)
 | |
|    or an existing shared memory block is attached (``False``).
 | |
| 
 | |
|    *size* specifies 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.
 | |
| 
 | |
|    .. method:: close()
 | |
| 
 | |
|       Closes access to the shared memory from this instance.  In order to
 | |
|       ensure proper cleanup of resources, all instances should call
 | |
|       ``close()`` once the instance is no longer needed.  Note that calling
 | |
|       ``close()`` does not cause the shared memory block itself to be
 | |
|       destroyed.
 | |
| 
 | |
|    .. method:: unlink()
 | |
| 
 | |
|       Requests that the underlying shared memory block be destroyed.  In
 | |
|       order to ensure proper cleanup of resources, ``unlink()`` should be
 | |
|       called once (and only once) across all processes which have need
 | |
|       for the shared memory block.  After requesting its destruction, a
 | |
|       shared memory block may or may not be immediately destroyed and
 | |
|       this behavior may differ across platforms.  Attempts to access data
 | |
|       inside the shared memory block after ``unlink()`` has been called may
 | |
|       result in memory access errors.  Note: the last process relinquishing
 | |
|       its hold on a shared memory block may call ``unlink()`` and
 | |
|       :meth:`close()` in either order.
 | |
| 
 | |
|    .. 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 ``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:`SharedMemory.unlink()` call on all of the
 | |
|    :class:`SharedMemory` objects managed by that process and then
 | |
|    stops the process itself.  By creating ``SharedMemory`` instances
 | |
|    through a ``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 ``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:`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:`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:`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)
 | |
| 
 | |
|    Provides a mutable list-like object where all values stored within are
 | |
|    stored in a shared memory block.  This constrains storable values to
 | |
|    only the ``int``, ``float``, ``bool``, ``str`` (less than 10M bytes each),
 | |
|    ``bytes`` (less than 10M bytes each), and ``None`` built-in data types.
 | |
|    It also notably differs from the built-in ``list`` type in that these
 | |
|    lists can not change their overall length (i.e. no append, insert, etc.)
 | |
|    and do not support the dynamic creation of new :class:`ShareableList`
 | |
|    instances via slicing.
 | |
| 
 | |
|    *sequence* is used in populating a new ``ShareableList`` full of values.
 | |
|    Set to ``None`` to instead attach to an already existing
 | |
|    ``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 ``ShareableList``, specify its shared memory block's unique
 | |
|    name while leaving ``sequence`` set to ``None``.
 | |
| 
 | |
|    .. method:: count(value)
 | |
| 
 | |
|       Returns the number of occurrences of ``value``.
 | |
| 
 | |
|    .. method:: index(value)
 | |
| 
 | |
|       Returns first index position of ``value``.  Raises :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 ``ShareableList``
 | |
| (and underlying ``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()
 | 
