multiprocessing.shared_memory --- حافظه مشترک برای دسترسی مستقیم بین فرآیندها

کد منبع: Lib/multiprocessing/shared_memory.py

اضافه شده در نسخه‌ی 3.8.


این ماژول یک کلاس، SharedMemory، را برای تخصیص و مدیریت حافظه مشترکی فراهم می‌کند که توسط یک یا چند فرایند بر روی یک ماشین چند هسته‌ای یا چندپردازنده‌ای متقارن (SMP) قابل دسترسی باشد. برای کمک به مدیریت چرخه عمر حافظه مشترک، به‌ویژه بین فرایندهای مجزا، یک زیرکلاس از BaseManager، یعنی SharedMemoryManager، نیز در ماژول multiprocessing.managers ارائه شده است.

در این ماژول، حافظه مشترک به بلوک‌های حافظه مشترک «سبک POSIX» اشاره دارد (هرچند لزوماً به‌طور صریح به این صورت پیاده‌سازی نشده است) و به «حافظه مشترک توزیع‌شده» اشاره نمی‌کند. این سبک از حافظه مشترک به فرایندهای مجزا اجازه می‌دهد که به‌طور بالقوه در یک ناحیه مشترک (یا به اشتراک گذاشته‌شده) از حافظه فرار بخوانند و بنویسند. فرایندها به‌طور قراردادی محدودند که فقط به فضای حافظه فرایند خود دسترسی داشته باشند، اما حافظه مشترک امکان اشتراک‌گذاری داده‌ها بین فرایندها را فراهم می‌کند و نیاز به ارسال پیام‌های حاوی آن داده‌ها بین فرایندها به‌عنوان جایگزین را از بین می‌برد. اشتراک‌گذاری مستقیم داده‌ها از طریق حافظه می‌تواند در مقایسه با اشتراک‌گذاری داده‌ها از طریق دیسک، سوکت یا سایر ارتباطاتی که به سریال‌سازی/سریال‌زدایی و کپی داده‌ها نیاز دارند، مزایای عملکردی قابل‌توجهی فراهم کند.

class multiprocessing.shared_memory.SharedMemory(name=None, create=False, size=0, *, track=True)

نمونه‌ای از کلاس SharedMemory برای ایجاد یک بلوک حافظه‌ی مشترک جدید یا اتصال به یک بلوک حافظه‌ی مشترک موجود ایجاد کنید. به هر بلوک حافظه‌ی مشترک یک نام یکتا اختصاص داده می‌شود. به این ترتیب، یک فرایند می‌تواند یک بلوک حافظه‌ی مشترک با یک نام مشخص ایجاد کند و فرایندی دیگر می‌تواند با استفاده از همان نام به همان بلوک حافظه‌ی مشترک متصل شود.

به‌عنوان منبعی برای اشتراک‌گذاری داده‌ها بین فرایندها، بلوک‌های حافظه مشترک ممکن است پس از پایان فرایند اصلی که آن‌ها را ایجاد کرده است همچنان باقی بمانند. هنگامی که یک فرایند دیگر نیازی به دسترسی به یک بلوک حافظه مشترک ندارد، در حالی که ممکن است هنوز فرایندهای دیگر به آن نیاز داشته باشند، باید متد close() فراخوانی شود. هنگامی که یک بلوک حافظه مشترک دیگر مورد نیاز هیچ فرایندی نیست، باید متد unlink() برای اطمینان از پاک‌سازی مناسب فراخوانی شود.

پارامترها:
  • name (str | None) -- نام یکتای حافظه مشترک درخواستی، که به‌صورت یک رشته مشخص می‌شود. هنگام ایجاد یک بلوک حافظه مشترک جدید، اگر None (پیش‌فرض) برای نام ارائه شود، یک نام جدید تولید خواهد شد.

  • create (bool) -- کنترل می‌کند که یک بلوک حافظه اشتراکی جدید ایجاد شود (True) یا یک بلوک حافظه اشتراکی موجود متصل شود (False).

  • size (int) -- تعداد بایت‌های درخواست‌شده هنگام ایجاد یک بلوک حافظه مشترک جدید. از آن‌جا که برخی سکوها تکه‌های حافظه را بر اساس اندازه صفحه حافظه آن سکو تخصیص می‌دهند، اندازه دقیق بلوک حافظه مشترک ممکن است بزرگ‌تر یا برابر با اندازه درخواست‌شده باشد. هنگام اتصال به یک بلوک حافظه مشترک موجود، پارامتر size نادیده گرفته می‌شود.

  • track (bool) -- هنگامی که True باشد، در سکوهایی که سیستم‌عامل این کار را به‌طور خودکار انجام نمی‌دهد، بلوک حافظه‌ی اشتراکی در یک فرایند پیگیری منابع ثبت می‌شود. فرایند پیگیری منابع، پاک‌سازی مناسب حافظه‌ی اشتراکی را تضمین می‌کند، حتی اگر همه‌ی فرایندهای دیگری که به حافظه دسترسی دارند، بدون انجام این کار خارج شوند. فرایندهای پایتونی که با استفاده از امکانات multiprocessing از یک جد مشترک ایجاد شده‌اند، یک فرایند پیگیری منابع واحد را به اشتراک می‌گذارند و طول عمر بخش‌های حافظه‌ی اشتراکی به‌طور خودکار در میان این فرایندها مدیریت می‌شود. فرایندهای پایتونی که به هر روش دیگری ایجاد شده‌اند، هنگام دسترسی به حافظه‌ی اشتراکی با فعال بودن track، فرایند پیگیری منابع اختصاصی خود را دریافت خواهند کرد. این امر باعث می‌شود حافظه‌ی اشتراکی توسط فرایند پیگیری منابعِ اولین فرایندی که خاتمه می‌یابد، حذف شود. برای اجتناب از این مشکل، کاربران subprocess یا فرایندهای پایتونی مستقل باید هنگامی که از قبل فرایند دیگری برای انجام ثبت و نگهداری وجود دارد، track را روی False تنظیم کنند. track در ویندوز در نظر گرفته نمی‌شود؛ ویندوز پیگیری خودش را دارد و هنگامی که همه‌ی دسته‌های آن بسته شده باشند، حافظه‌ی اشتراکی را به‌طور خودکار حذف می‌کند.

تغییر یافته در نسخه‌ی 3.13: پارامتر track افزوده شد.

close()

توصیف‌گر/دسته‌ی فایلِ حافظه‌ی مشترک را از این نمونه ببندید. هنگامی که دیگر نیازی به دسترسی به بلوک حافظه‌ی مشترک از این نمونه نباشد، باید close() فراخوانی شود. بسته به سیستم‌عامل، حتی اگر همه‌ی دسته‌های آن بسته شده باشند، ممکن است حافظه‌ی زیرین آزاد شود یا نشود. برای اطمینان از پاک‌سازی صحیح، از متد unlink() استفاده کنید.

بلوک حافظه مشترک زیربنایی را حذف می‌کند. این متد باید فقط ۱ بار به‌ازای هر بلوک حافظه مشترک فراخوانی شود، صرف‌نظر از تعداد دسته‌های آن، حتی در فرآیندهای دیگر. می‌توان unlink() و close() را با هر ترتیبی فراخوانی کرد، اما تلاش برای دسترسی به داده‌های درون یک بلوک حافظه مشترک پس از unlink() ممکن است به خطاهای دسترسی به حافظه منجر شود، بسته به پلتفرم.

این متد در ویندوز تأثیری ندارد؛ تنها راه حذف یک بلوک حافظه مشترک در آن، بستن تمام دسته‌ها است.

buf

یک memoryview از محتویات بلوک حافظه‌ی مشترک.

name

دسترسی فقط‌خواندنی به نام یکتای بلوک حافظه‌ی مشترک.

size

دسترسی فقط‌خواندنی به اندازه‌ی بلوک حافظه مشترک بر حسب بایت.

مثال زیر استفاده سطح پایین از نمونه‌های SharedMemory را نشان می‌دهد:

>>> 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

مثال زیر یک کاربرد عملی از کلاس SharedMemory با آرایه‌های NumPy را نشان می‌دهد، که در آن همان numpy.ndarray از دو پوسته‌ی پایتون جداگانه قابل دسترسی است:

>>> # 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 multiprocessing.managers.SharedMemoryManager([address[, authkey]])

یک کلاس فرعی از multiprocessing.managers.BaseManager که می‌توان از آن برای مدیریت بلوک‌های حافظه مشترک بین فرایندها استفاده کرد.

فراخوانی start() روی یک نمونه SharedMemoryManager باعث شروع یک فرایند جدید می‌شود. تنها هدف این فرایند جدید، مدیریت چرخه عمر تمام بلوک‌های حافظه مشترک ایجادشده از طریق آن است. برای آزادسازی تمام بلوک‌های حافظه مشترک مدیریت‌شده توسط آن فرایند، shutdown() را روی این نمونه فراخوانی کنید. این کار باعث فراخوانی unlink() روی تمام اشیاء SharedMemory مدیریت‌شده توسط آن فرایند می‌شود و سپس خود فرایند را متوقف می‌کند. با ایجاد نمونه‌های SharedMemory از طریق یک SharedMemoryManager، از نیاز به پیگیری دستی و آزادسازی منابع حافظه مشترک بی‌نیاز می‌شویم.

این کلاس متدهایی را برای ایجاد و برگرداندن نمونه‌های SharedMemory و برای ایجاد یک شیء فهرست‌مانند (ShareableList) مبتنی بر حافظه مشترک فراهم می‌کند.

برای توضیح آرگومان‌های ورودی اختیاری address و authkey به‌ارث‌رسیده و نحوه‌ی استفاده از آن‌ها برای اتصال به یک سرویس SharedMemoryManager موجود از فرایندهای دیگر، به BaseManager مراجعه کنید.

SharedMemory(size)

یک شیء SharedMemory جدید با size مشخص‌شده بر حسب بایت ایجاد می‌کند و بازمی‌گرداند.

ShareableList(sequence)

یک شیء جدید ShareableList را ایجاد و برمی‌گرداند که با مقادیر sequence ورودی مقداردهی اولیه شده است.

مثال زیر سازوکارهای پایه‌ی یک SharedMemoryManager را نشان می‌دهد:

>>> 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

مثال زیر الگویی احتمالاً راحت‌تر برای استفاده از اشیای SharedMemoryManager از طریق دستور with را نشان می‌دهد تا اطمینان حاصل شود که تمام بلوک‌های حافظه مشترک پس از این‌که دیگر نیازی به آن‌ها نیست، آزاد می‌شوند:

>>> 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

هنگامی که از یک SharedMemoryManager در یک دستور with استفاده می‌کنید، تمام بلوک‌های حافظه‌ی مشترک ایجادشده با استفاده از آن مدیر، زمانی که اجرای بلوک آپ‌کد with به پایان می‌رسد، آزاد می‌شوند.

class multiprocessing.shared_memory.ShareableList(sequence=None, *, name=None)

یک شیء فهرست‌مانند تغییرپذیر فراهم می‌کند که همه مقادیر ذخیره‌شده در آن، در یک بلوک حافظه مشترک ذخیره می‌شوند. این موضوع مقادیر قابل ذخیره را به انواع داده توکار زیر محدود می‌کند:

  • int (۶۴‌بیتی علامت‌دار)

  • float

  • bool

  • str (هرکدام کمتر از ۱۰ مگابایت هنگام کدگذاری به‌صورت UTF-8)

  • bytes (هر کدام کمتر از ۱۰M بایت)

  • None

همچنین به‌طور قابل‌توجهی با نوع list توکار تفاوت دارد، از این جهت که این فهرست‌ها نمی‌توانند طول کلی خود را تغییر دهند (یعنی append()، insert() و غیره وجود ندارند) و از ایجاد پویای نمونه‌های جدید ShareableList از طریق اسلایس پشتیبانی نمی‌کنند.

sequence برای پر کردن یک ShareableList جدید از مقادیر استفاده می‌شود. در عوض، برای اتصال به یک ShareableList از پیش موجود از طریق نام یکتای حافظه مشترک آن، آن را روی None تنظیم کنید.

name نام یکتای حافظه مشترک درخواست‌شده است، همان‌طور که در تعریف SharedMemory توضیح داده شده است. هنگام اتصال به یک ShareableList موجود، نام یکتای بلوک حافظه مشترک آن را مشخص کنید، در حالی که sequence را روی None باقی می‌گذارید.

توجه

یک مشکل شناخته‌شده برای مقادیر bytes و str وجود دارد. اگر آن‌ها با بایت‌ها یا نویسه‌های تهی (nul) \x00 پایان یابند، ممکن است هنگام واکشی آن‌ها با اندیس از ShareableList، به‌صورت خاموش حذف شوند. این رفتار .rstrip(b'\x00') به‌عنوان یک باگ در نظر گرفته می‌شود و ممکن است در آینده از بین برود. به gh-106939 مراجعه کنید.

برای برنامه‌های کاربردی که حذف از راست (rstripping) نویسه‌های null انتهایی در آن‌ها مشکل‌ساز است، با افزودن همیشگی و بی‌قیدوشرط یک بایت اضافی غیرصفر به انتهای چنین مقادیری هنگام ذخیره‌سازی و حذف بی‌قیدوشرط آن هنگام واکشی، این مشکل را دور بزنید:

>>> 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()
count(value)

تعداد رخدادهای value را برمی‌گرداند.

index(value)

اولین موقعیت اندیس value را برمی‌گرداند. اگر value موجود نباشد، ValueError را پرتاب می‌کند.

format

ویژگی فقط‌خواندنی که شامل قالب بسته‌بندی struct استفاده‌شده برای همه‌ی مقادیر ذخیره‌شده‌ی کنونی است.

shm

نمونه‌ی SharedMemory که مقدارها در آن ذخیره شده‌اند.

مثال زیر استفاده‌ی مقدماتی از یک نمونه‌ی ShareableList را نشان می‌دهد:

>>> 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

مثال زیر نشان می‌دهد که چگونه یک، دو یا چند فرایند می‌توانند با ارائه نام بلوک حافظه مشترک پشت آن، به همان ShareableList دسترسی یابند:

>>> 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()

مثال‌های زیر نشان می‌دهند که اشیاء ShareableListSharedMemory زیربنایی) را می‌توان در صورت نیاز پیکل و پیکل‌گشایی (unpickle) کرد. توجه داشته باشید که این شیء همچنان همان شیء مشترک خواهد بود. این اتفاق به این دلیل رخ می‌دهد که شیء پیکل‌گشایی‌شده دارای همان نام یکتا است و صرفاً به یک شیء موجود با همان نام متصل می‌شود (اگر شیء هنوز زنده باشد):

>>> 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()