मैं पायथन 3.8 के नए shared_memory मॉड्यूल का उपयोग कर रहा हूं और साझा मेमोरी को बिना प्रक्रियाओं को समाप्त किए मुक्त करने में विफल हूं।

साझा स्मृति का एक ब्लॉक shm बनाने और उपयोग करने के बाद, मैं इसे सभी प्रक्रियाओं में shm.close() के माध्यम से बंद कर देता हूं और अंत में इसे मुख्य प्रक्रिया में shm.unlink के माध्यम से मुक्त करता हूं। हालांकि, संसाधन मॉनिटर मुझे दिखाता है कि कार्यक्रम समाप्त होने तक स्मृति मुक्त नहीं होती है। यह मेरे लिए एक गंभीर समस्या है, क्योंकि मेरे कार्यक्रम को लंबे समय तक चलने की जरूरत है। निम्न प्रोग्राम के साथ विंडोज/पायथन 3.8 पर समस्या को पुन: प्रस्तुत किया जा सकता है:

from multiprocessing import shared_memory, Pool
from itertools import repeat
from time import sleep

def fun(dummy, name):
    
    # access shared memory
    shm = shared_memory.SharedMemory(name=name)
    
    # do work
    sleep(1)
    
    # release shared memory
    shm.close()
    
    return dummy

def meta_fun(pool):
    
    # create shared array
    arr = shared_memory.SharedMemory(create=True, size=500000000)
    
    # compute result
    result = sum(pool.starmap(fun, zip(range(10), repeat(arr.name))))
    
    # release and free memory
    arr.close()
    arr.unlink()
    
    return result

if __name__ == '__main__':
    
    # use one Pool for many method calls to save the time for repeatedly
    # creating processes
    with Pool() as pool:
        for i in range(100):
            print(meta_fun(pool))

सावधानी: इस स्क्रिप्ट को निष्पादित करते समय, आप जल्दी से अपनी पूरी मेमोरी भर सकते हैं! संसाधन मॉनीटर में "वर्चुअल मेमोरी" पैनल देखें।

कुछ शोध करने के बाद, मुझे पता चला कि (1) unlink() फ़ंक्शन Windows पर कुछ नहीं करता:

def unlink(self):
    """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 access
    to the shared memory block."""
    if _USE_POSIX and self._name:
        from .resource_tracker import unregister
        _posixshmem.shm_unlink(self._name)
        unregister(self._name, "shared_memory")

और (2) विंडोज़ साझा स्मृति को मुक्त करने लगता है जब इसे बनाने/उपयोग करने वाली प्रक्रियाएं बंद हो जाती हैं (टिप्पणियां देखें यहां और यहां)। यह पाइथन के स्पष्ट रूप से इसे संभालने का कारण नहीं हो सकता है।

जवाब में मैंने एक ही साझा मेमोरी ब्लॉक को बार-बार अनलिंक किए बिना सहेजने और पुन: उपयोग करके एक बदसूरत कामकाज बनाया है। जाहिर है, यह एक संतोषजनक समाधान नहीं है, खासकर यदि आवश्यक मेमोरी ब्लॉक के आकार गतिशील रूप से बदलते हैं।

क्या ऐसा कोई तरीका है जिससे मैं विंडोज़ पर साझा की गई मेमोरी को मैन्युअल रूप से खाली कर सकूं?

3
Samufi 30 जिंदा 2021, 17:08

1 उत्तर

सबसे बढ़िया उत्तर

यह multiprocessing मॉड्यूल में एक बग है, जिसकी रिपोर्ट समस्या 40882 के रूप में की गई थी। एक खुला पुल अनुरोध है जो इसे ठीक करता है, PR 20684, हालांकि जाहिर तौर पर यह धीमा रहा है विलय करने के लिए।

बग इस प्रकार है: SharedMemory.__init__ में, हमारे पास एक आह्वान noreferrer">MapViewOfFile API बिना संगत UnmapViewOfFile, और mmap ऑब्जेक्ट भी इसका स्वामित्व नहीं लेता है (यह ब्लॉक को फिर से अपने आप मैप करता है)।

इस बीच, आप shared_memory मॉड्यूल को बंदर-पैच कर सकते हैं ताकि mmap के निर्माण के बाद लापता UnmapViewOfFile कॉल जुड़ जाए। आपको शायद ctypes पर निर्भर रहना पड़ेगा, क्योंकि _winapi मॉड्यूल UnmapViewOfFile निर्यात नहीं करता है, MapViewOfFile (!) का निर्यात करने के बावजूद। ऐसा कुछ (परीक्षण नहीं किया गया):

import ctypes, ctypes.wintypes
import multiprocessing, multiprocessing.shared_memory

UnmapViewOfFile = ctypes.windll.kernel32.UnmapViewOfFile
UnmapViewOfFile.argtypes = (ctypes.wintypes.LPCVOID,)
UnmapViewOfFile.restype = ctypes.wintypes.BOOL

def _SharedMemory_init(self, name=None, create=False, size=0):
    ... # copy from SharedMemory.__init__ in the original module
                try:
                    p_buf = _winapi.MapViewOfFile(
                        h_map,
                        _winapi.FILE_MAP_READ,
                        0,
                        0,
                        0
                    )
                finally:
                    _winapi.CloseHandle(h_map)
                try:
                    size = _winapi.VirtualQuerySize(p_buf)
                    self._mmap = mmap.mmap(-1, size, tagname=name)
                finally:
                    UnmapViewOfFile(p_buf)
    ... # copy from SharedMemory.__init__ in the original module

multiprocessing.shared_memory.SharedMemory.__init__ = _SharedMemory_init

उपरोक्त कोड को एक मॉड्यूल में रखें और multiprocessing मॉड्यूल से कुछ भी उपयोग करने से पहले इसे लोड करना याद रखें। वैकल्पिक रूप से, आप आवश्यक UnmapViewOfFile कॉल को शामिल करने के लिए multiprocessing मॉड्यूल की निर्देशिका में सीधे shared_memory.py फ़ाइल को संपादित कर सकते हैं। यह सबसे साफ समाधान नहीं है, लेकिन यह वैसे भी अस्थायी होना है (प्रसिद्ध अंतिम शब्द); दीर्घकालिक समाधान यह है कि यह निश्चित अपस्ट्रीम (जैसा कि स्पष्ट रूप से चल रहा है)।

7
user3840170 7 फरवरी 2021, 20:45
मैंने इसका परीक्षण किया है। आकर्षण की तरह काम करता है!
 – 
Samufi
8 फरवरी 2021, 13:24