|
| 1 | +:mod:`multiprocessing.shared_memory` --- Provides shared memory for direct access across processes |
| 2 | +=================================================================================================== |
| 3 | + |
| 4 | +.. module:: multiprocessing.shared_memory |
| 5 | + :synopsis: Provides shared memory for direct access across processes. |
| 6 | + |
| 7 | +**Source code:** :source:`Lib/multiprocessing/shared_memory.py` |
| 8 | + |
| 9 | +.. versionadded:: 3.8 |
| 10 | + |
| 11 | +.. index:: |
| 12 | + single: Shared Memory |
| 13 | + single: POSIX Shared Memory |
| 14 | + single: Named Shared Memory |
| 15 | + |
| 16 | +-------------- |
| 17 | + |
| 18 | +This module provides a class, :class:`SharedMemory`, for the allocation |
| 19 | +and management of shared memory to be accessed by one or more processes |
| 20 | +on a multicore or symmetric multiprocessor (SMP) machine. To assist with |
| 21 | +the life-cycle management of shared memory especially across distinct |
| 22 | +processes, a :class:`~multiprocessing.managers.BaseManager` subclass, |
| 23 | +:class:`SharedMemoryManager`, is also provided in the |
| 24 | +``multiprocessing.managers`` module. |
| 25 | + |
| 26 | +In this module, shared memory refers to "System V style" shared memory blocks |
| 27 | +(though is not necessarily implemented explicitly as such) and does not refer |
| 28 | +to "distributed shared memory". This style of shared memory permits distinct |
| 29 | +processes to potentially read and write to a common (or shared) region of |
| 30 | +volatile memory. Processes are conventionally limited to only have access to |
| 31 | +their own process memory space but shared memory permits the sharing |
| 32 | +of data between processes, avoiding the need to instead send messages between |
| 33 | +processes containing that data. Sharing data directly via memory can provide |
| 34 | +significant performance benefits compared to sharing data via disk or socket |
| 35 | +or other communications requiring the serialization/deserialization and |
| 36 | +copying of data. |
| 37 | + |
| 38 | + |
| 39 | +.. class:: SharedMemory(name=None, create=False, size=0) |
| 40 | + |
| 41 | + Creates a new shared memory block or attaches to an existing shared |
| 42 | + memory block. Each shared memory block is assigned a unique name. |
| 43 | + In this way, one process can create a shared memory block with a |
| 44 | + particular name and a different process can attach to that same shared |
| 45 | + memory block using that same name. |
| 46 | + |
| 47 | + As a resource for sharing data across processes, shared memory blocks |
| 48 | + may outlive the original process that created them. When one process |
| 49 | + no longer needs access to a shared memory block that might still be |
| 50 | + needed by other processes, the :meth:`close()` method should be called. |
| 51 | + When a shared memory block is no longer needed by any process, the |
| 52 | + :meth:`unlink()` method should be called to ensure proper cleanup. |
| 53 | + |
| 54 | + *name* is the unique name for the requested shared memory, specified as |
| 55 | + a string. When creating a new shared memory block, if ``None`` (the |
| 56 | + default) is supplied for the name, a novel name will be generated. |
| 57 | + |
| 58 | + *create* controls whether a new shared memory block is created (``True``) |
| 59 | + or an existing shared memory block is attached (``False``). |
| 60 | + |
| 61 | + *size* specifies the requested number of bytes when creating a new shared |
| 62 | + memory block. Because some platforms choose to allocate chunks of memory |
| 63 | + based upon that platform's memory page size, the exact size of the shared |
| 64 | + memory block may be larger or equal to the size requested. When attaching |
| 65 | + to an existing shared memory block, the ``size`` parameter is ignored. |
| 66 | + |
| 67 | + .. method:: close() |
| 68 | + |
| 69 | + Closes access to the shared memory from this instance. In order to |
| 70 | + ensure proper cleanup of resources, all instances should call |
| 71 | + ``close()`` once the instance is no longer needed. Note that calling |
| 72 | + ``close()`` does not cause the shared memory block itself to be |
| 73 | + destroyed. |
| 74 | + |
| 75 | + .. method:: unlink() |
| 76 | + |
| 77 | + Requests that the underlying shared memory block be destroyed. In |
| 78 | + order to ensure proper cleanup of resources, ``unlink()`` should be |
| 79 | + called once (and only once) across all processes which have need |
| 80 | + for the shared memory block. After requesting its destruction, a |
| 81 | + shared memory block may or may not be immediately destroyed and |
| 82 | + this behavior may differ across platforms. Attempts to access data |
| 83 | + inside the shared memory block after ``unlink()`` has been called may |
| 84 | + result in memory access errors. Note: the last process relinquishing |
| 85 | + its hold on a shared memory block may call ``unlink()`` and |
| 86 | + :meth:`close()` in either order. |
| 87 | + |
| 88 | + .. attribute:: buf |
| 89 | + |
| 90 | + A memoryview of contents of the shared memory block. |
| 91 | + |
| 92 | + .. attribute:: name |
| 93 | + |
| 94 | + Read-only access to the unique name of the shared memory block. |
| 95 | + |
| 96 | + .. attribute:: size |
| 97 | + |
| 98 | + Read-only access to size in bytes of the shared memory block. |
| 99 | + |
| 100 | + |
| 101 | +The following example demonstrates low-level use of :class:`SharedMemory` |
| 102 | +instances:: |
| 103 | + |
| 104 | + >>> from multiprocessing import shared_memory |
| 105 | + >>> shm_a = shared_memory.SharedMemory(create=True, size=10) |
| 106 | + >>> type(shm_a.buf) |
| 107 | + <class 'memoryview'> |
| 108 | + >>> buffer = shm_a.buf |
| 109 | + >>> len(buffer) |
| 110 | + 10 |
| 111 | + >>> buffer[:4] = bytearray([22, 33, 44, 55]) # Modify multiple at once |
| 112 | + >>> buffer[4] = 100 # Modify single byte at a time |
| 113 | + >>> # Attach to an existing shared memory block |
| 114 | + >>> shm_b = shared_memory.SharedMemory(shm_a.name) |
| 115 | + >>> import array |
| 116 | + >>> array.array('b', shm_b.buf[:5]) # Copy the data into a new array.array |
| 117 | + array('b', [22, 33, 44, 55, 100]) |
| 118 | + >>> shm_b.buf[:5] = b'howdy' # Modify via shm_b using bytes |
| 119 | + >>> bytes(shm_a.buf[:5]) # Access via shm_a |
| 120 | + b'howdy' |
| 121 | + >>> shm_b.close() # Close each SharedMemory instance |
| 122 | + >>> shm_a.close() |
| 123 | + >>> shm_a.unlink() # Call unlink only once to release the shared memory |
| 124 | + |
| 125 | + |
| 126 | + |
| 127 | +The following example demonstrates a practical use of the :class:`SharedMemory` |
| 128 | +class with `NumPy arrays <https://www.numpy.org/>`_, accessing the |
| 129 | +same ``numpy.ndarray`` from two distinct Python shells: |
| 130 | + |
| 131 | +.. doctest:: |
| 132 | + :options: +SKIP |
| 133 | + |
| 134 | + >>> # In the first Python interactive shell |
| 135 | + >>> import numpy as np |
| 136 | + >>> a = np.array([1, 1, 2, 3, 5, 8]) # Start with an existing NumPy array |
| 137 | + >>> from multiprocessing import shared_memory |
| 138 | + >>> shm = shared_memory.SharedMemory(create=True, size=a.nbytes) |
| 139 | + >>> # Now create a NumPy array backed by shared memory |
| 140 | + >>> b = np.ndarray(a.shape, dtype=a.dtype, buffer=shm.buf) |
| 141 | + >>> b[:] = a[:] # Copy the original data into shared memory |
| 142 | + >>> b |
| 143 | + array([1, 1, 2, 3, 5, 8]) |
| 144 | + >>> type(b) |
| 145 | + <class 'numpy.ndarray'> |
| 146 | + >>> type(a) |
| 147 | + <class 'numpy.ndarray'> |
| 148 | + >>> shm.name # We did not specify a name so one was chosen for us |
| 149 | + 'psm_21467_46075' |
| 150 | + |
| 151 | + >>> # In either the same shell or a new Python shell on the same machine |
| 152 | + >>> import numpy as np |
| 153 | + >>> from multiprocessing import shared_memory |
| 154 | + >>> # Attach to the existing shared memory block |
| 155 | + >>> existing_shm = shared_memory.SharedMemory(name='psm_21467_46075') |
| 156 | + >>> # Note that a.shape is (6,) and a.dtype is np.int64 in this example |
| 157 | + >>> c = np.ndarray((6,), dtype=np.int64, buffer=existing_shm.buf) |
| 158 | + >>> c |
| 159 | + array([1, 1, 2, 3, 5, 8]) |
| 160 | + >>> c[-1] = 888 |
| 161 | + >>> c |
| 162 | + array([ 1, 1, 2, 3, 5, 888]) |
| 163 | + |
| 164 | + >>> # Back in the first Python interactive shell, b reflects this change |
| 165 | + >>> b |
| 166 | + array([ 1, 1, 2, 3, 5, 888]) |
| 167 | + |
| 168 | + >>> # Clean up from within the second Python shell |
| 169 | + >>> del c # Unnecessary; merely emphasizing the array is no longer used |
| 170 | + >>> existing_shm.close() |
| 171 | + |
| 172 | + >>> # Clean up from within the first Python shell |
| 173 | + >>> del b # Unnecessary; merely emphasizing the array is no longer used |
| 174 | + >>> shm.close() |
| 175 | + >>> shm.unlink() # Free and release the shared memory block at the very end |
| 176 | + |
| 177 | + |
| 178 | +.. class:: SharedMemoryManager([address[, authkey]]) |
| 179 | + |
| 180 | + A subclass of :class:`~multiprocessing.managers.BaseManager` which can be |
| 181 | + used for the management of shared memory blocks across processes. |
| 182 | + |
| 183 | + A call to :meth:`~multiprocessing.managers.BaseManager.start` on a |
| 184 | + :class:`SharedMemoryManager` instance causes a new process to be started. |
| 185 | + This new process's sole purpose is to manage the life cycle |
| 186 | + of all shared memory blocks created through it. To trigger the release |
| 187 | + of all shared memory blocks managed by that process, call |
| 188 | + :meth:`~multiprocessing.managers.BaseManager.shutdown()` on the instance. |
| 189 | + This triggers a :meth:`SharedMemory.unlink()` call on all of the |
| 190 | + :class:`SharedMemory` objects managed by that process and then |
| 191 | + stops the process itself. By creating ``SharedMemory`` instances |
| 192 | + through a ``SharedMemoryManager``, we avoid the need to manually track |
| 193 | + and trigger the freeing of shared memory resources. |
| 194 | + |
| 195 | + This class provides methods for creating and returning :class:`SharedMemory` |
| 196 | + instances and for creating a list-like object (:class:`ShareableList`) |
| 197 | + backed by shared memory. |
| 198 | + |
| 199 | + Refer to :class:`multiprocessing.managers.BaseManager` for a description |
| 200 | + of the inherited *address* and *authkey* optional input arguments and how |
| 201 | + they may be used to connect to an existing ``SharedMemoryManager`` service |
| 202 | + from other processes. |
| 203 | + |
| 204 | + .. method:: SharedMemory(size) |
| 205 | + |
| 206 | + Create and return a new :class:`SharedMemory` object with the |
| 207 | + specified ``size`` in bytes. |
| 208 | + |
| 209 | + .. method:: ShareableList(sequence) |
| 210 | + |
| 211 | + Create and return a new :class:`ShareableList` object, initialized |
| 212 | + by the values from the input ``sequence``. |
| 213 | + |
| 214 | + |
| 215 | +The following example demonstrates the basic mechanisms of a |
| 216 | +:class:`SharedMemoryManager`: |
| 217 | + |
| 218 | +.. doctest:: |
| 219 | + :options: +SKIP |
| 220 | + |
| 221 | + >>> from multiprocessing import shared_memory |
| 222 | + >>> smm = shared_memory.SharedMemoryManager() |
| 223 | + >>> smm.start() # Start the process that manages the shared memory blocks |
| 224 | + >>> sl = smm.ShareableList(range(4)) |
| 225 | + >>> sl |
| 226 | + ShareableList([0, 1, 2, 3], name='psm_6572_7512') |
| 227 | + >>> raw_shm = smm.SharedMemory(size=128) |
| 228 | + >>> another_sl = smm.ShareableList('alpha') |
| 229 | + >>> another_sl |
| 230 | + ShareableList(['a', 'l', 'p', 'h', 'a'], name='psm_6572_12221') |
| 231 | + >>> smm.shutdown() # Calls unlink() on sl, raw_shm, and another_sl |
| 232 | + |
| 233 | +The following example depicts a potentially more convenient pattern for using |
| 234 | +:class:`SharedMemoryManager` objects via the :keyword:`with` statement to |
| 235 | +ensure that all shared memory blocks are released after they are no longer |
| 236 | +needed: |
| 237 | + |
| 238 | +.. doctest:: |
| 239 | + :options: +SKIP |
| 240 | + |
| 241 | + >>> with shared_memory.SharedMemoryManager() as smm: |
| 242 | + ... sl = smm.ShareableList(range(2000)) |
| 243 | + ... # Divide the work among two processes, storing partial results in sl |
| 244 | + ... p1 = Process(target=do_work, args=(sl, 0, 1000)) |
| 245 | + ... p2 = Process(target=do_work, args=(sl, 1000, 2000)) |
| 246 | + ... p1.start() |
| 247 | + ... p2.start() # A multiprocessing.Pool might be more efficient |
| 248 | + ... p1.join() |
| 249 | + ... p2.join() # Wait for all work to complete in both processes |
| 250 | + ... total_result = sum(sl) # Consolidate the partial results now in sl |
| 251 | + |
| 252 | +When using a :class:`SharedMemoryManager` in a :keyword:`with` statement, the |
| 253 | +shared memory blocks created using that manager are all released when the |
| 254 | +:keyword:`with` statement's code block finishes execution. |
| 255 | + |
| 256 | + |
| 257 | +.. class:: ShareableList(sequence=None, *, name=None) |
| 258 | + |
| 259 | + Provides a mutable list-like object where all values stored within are |
| 260 | + stored in a shared memory block. This constrains storable values to |
| 261 | + only the ``int``, ``float``, ``bool``, ``str`` (less than 10M bytes each), |
| 262 | + ``bytes`` (less than 10M bytes each), and ``None`` built-in data types. |
| 263 | + It also notably differs from the built-in ``list`` type in that these |
| 264 | + lists can not change their overall length (i.e. no append, insert, etc.) |
| 265 | + and do not support the dynamic creation of new :class:`ShareableList` |
| 266 | + instances via slicing. |
| 267 | + |
| 268 | + *sequence* is used in populating a new ``ShareableList`` full of values. |
| 269 | + Set to ``None`` to instead attach to an already existing |
| 270 | + ``ShareableList`` by its unique shared memory name. |
| 271 | + |
| 272 | + *name* is the unique name for the requested shared memory, as described |
| 273 | + in the definition for :class:`SharedMemory`. When attaching to an |
| 274 | + existing ``ShareableList``, specify its shared memory block's unique |
| 275 | + name while leaving ``sequence`` set to ``None``. |
| 276 | + |
| 277 | + .. method:: count(value) |
| 278 | + |
| 279 | + Returns the number of occurrences of ``value``. |
| 280 | + |
| 281 | + .. method:: index(value) |
| 282 | + |
| 283 | + Returns first index position of ``value``. Raises :exc:`ValueError` if |
| 284 | + ``value`` is not present. |
| 285 | + |
| 286 | + .. attribute:: format |
| 287 | + |
| 288 | + Read-only attribute containing the :mod:`struct` packing format used by |
| 289 | + all currently stored values. |
| 290 | + |
| 291 | + .. attribute:: shm |
| 292 | + |
| 293 | + The :class:`SharedMemory` instance where the values are stored. |
| 294 | + |
| 295 | + |
| 296 | +The following example demonstrates basic use of a :class:`ShareableList` |
| 297 | +instance: |
| 298 | + |
| 299 | + >>> from multiprocessing import shared_memory |
| 300 | + >>> a = shared_memory.ShareableList(['howdy', b'HoWdY', -273.154, 100, None, True, 42]) |
| 301 | + >>> [ type(entry) for entry in a ] |
| 302 | + [<class 'str'>, <class 'bytes'>, <class 'float'>, <class 'int'>, <class 'NoneType'>, <class 'bool'>, <class 'int'>] |
| 303 | + >>> a[2] |
| 304 | + -273.154 |
| 305 | + >>> a[2] = -78.5 |
| 306 | + >>> a[2] |
| 307 | + -78.5 |
| 308 | + >>> a[2] = 'dry ice' # Changing data types is supported as well |
| 309 | + >>> a[2] |
| 310 | + 'dry ice' |
| 311 | + >>> a[2] = 'larger than previously allocated storage space' |
| 312 | + Traceback (most recent call last): |
| 313 | + ... |
| 314 | + ValueError: exceeds available storage for existing str |
| 315 | + >>> a[2] |
| 316 | + 'dry ice' |
| 317 | + >>> len(a) |
| 318 | + 7 |
| 319 | + >>> a.index(42) |
| 320 | + 6 |
| 321 | + >>> a.count(b'howdy') |
| 322 | + 0 |
| 323 | + >>> a.count(b'HoWdY') |
| 324 | + 1 |
| 325 | + >>> a.shm.close() |
| 326 | + >>> a.shm.unlink() |
| 327 | + >>> del a # Use of a ShareableList after call to unlink() is unsupported |
| 328 | + |
| 329 | +The following example depicts how one, two, or many processes may access the |
| 330 | +same :class:`ShareableList` by supplying the name of the shared memory block |
| 331 | +behind it: |
| 332 | + |
| 333 | + >>> b = shared_memory.ShareableList(range(5)) # In a first process |
| 334 | + >>> c = shared_memory.ShareableList(name=b.shm.name) # In a second process |
| 335 | + >>> c |
| 336 | + ShareableList([0, 1, 2, 3, 4], name='...') |
| 337 | + >>> c[-1] = -999 |
| 338 | + >>> b[-1] |
| 339 | + -999 |
| 340 | + >>> b.shm.close() |
| 341 | + >>> c.shm.close() |
| 342 | + >>> c.shm.unlink() |
| 343 | + |
0 commit comments