簡體   English   中英

多處理中的共享內存

[英]Shared memory in multiprocessing

我有三個大名單。 第一個包含位數組(模塊 bitarray 0.8.0),另外兩個包含整數數組。

l1=[bitarray 1, bitarray 2, ... ,bitarray n]
l2=[array 1, array 2, ... , array n]
l3=[array 1, array 2, ... , array n]

這些數據結構需要相當多的 RAM(總共約 16GB)。

如果我使用以下方法啟動 12 個子流程:

multiprocessing.Process(target=someFunction, args=(l1,l2,l3))

這是否意味着將為每個子進程復制 l1、l2 和 l3,或者子進程將共享這些列表? 或者更直接地說,我會使用 16GB 還是 192GB 的 RAM?

someFunction 將從這些列表中讀取一些值,然后根據讀取的值執行一些計算。 結果將返回給父進程。 列表 l1、l2 和 l3 不會被 someFunction 修改。

因此,我會假設子流程不需要也不會復制這些巨大的列表,而只會與父進程共享它們。 這意味着由於 linux 下的寫時復制方法,該程序將占用 16GB 的 RAM(無論我啟動了多少個子進程)? 我是正確的還是我遺漏了一些會導致列表被復制的東西?

編輯:在閱讀更多關於該主題的內容后,我仍然感到困惑。 一方面,Linux 使用寫時復制,這意味着不會復制任何數據。 另一方面,訪問對象會改變它的引用計數(我仍然不確定為什么以及這意味着什么)。 即便如此,整個對象會被復制嗎?

例如,如果我定義 someFunction 如下:

def someFunction(list1, list2, list3):
    i=random.randint(0,99999)
    print list1[i], list2[i], list3[i]

使用此函數是否意味着將為每個子進程完全復制 l1、l2 和 l3?

有沒有辦法檢查這個?

EDIT2在子進程運行時閱讀更多內容並監視系統的總內存使用情況后,似乎確實為每個子進程復制了整個對象。 這似乎是因為引用計數。

在我的程序中實際上不需要 l1、l2 和 l3 的引用計數。 這是因為 l1、l2 和 l3 將保存在內存中(不變),直到父進程退出。 在此之前無需釋放這些列表使用的內存。 事實上,我確信引用計數將保持在 0 以上(對於這些列表和這些列表中的每個對象),直到程序退出。

所以現在問題變成了,我如何確保對象不會被復制到每個子進程? 我可以禁用這些列表和這些列表中的每個對象的引用計數嗎?

EDIT3只是一個附加說明。 子流程不需要修改l1l2l3或這些列表中的任何對象。 子進程只需要能夠引用其中一些對象,而不會導致為每個子進程復制內存。

一般來說,共享相同的數據有兩種方式:

  • 多線程
  • 共享內存

Python 的多線程不適合 CPU 密集型任務(因為 GIL),所以在這種情況下通常的解決方案是繼續multiprocessing 但是,使用此解決方案,您需要使用multiprocessing.Valuemultiprocessing.Array顯式共享數據。

請注意,由於所有同步問題,通常在進程之間共享數據可能不是最佳選擇; 參與者交換信息的方法通常被視為更好的選擇。 另請參閱Python 文檔

如上所述,在進行並發編程時,通常最好盡可能避免使用共享狀態。 使用多個進程時尤其如此。

但是,如果您確實需要使用一些共享數據,那么多處理提供了幾種這樣做的方法。

在您的情況下,您需要以multiprocessing可以理解的某種方式包裝l1l2l3 (例如,通過使用multiprocessing.Array ),然后將它們作為參數傳遞。
另請注意,正如您所說,您不需要寫訪問權限,那么您應該在創建對象時傳遞lock=False ,否則所有訪問權限仍將被序列化。

因為這在 google 上仍然是一個非常高的結果,而且還沒有其他人提到它,我想我會提到在 python 3.8.0 版中引入的“真實”共享內存的新可能性: https://docs.python .org/3/library/multiprocessing.shared_memory.html

我在這里包含了一個使用 numpy 數組的小型人為示例(在 linux 上測試),這可能是一個非常常見的用例:

# one dimension of the 2d array which is shared
dim = 5000

import numpy as np
from multiprocessing import shared_memory, Process, Lock
from multiprocessing import cpu_count, current_process
import time

lock = Lock()

def add_one(shr_name):

    existing_shm = shared_memory.SharedMemory(name=shr_name)
    np_array = np.ndarray((dim, dim,), dtype=np.int64, buffer=existing_shm.buf)
    lock.acquire()
    np_array[:] = np_array[0] + 1
    lock.release()
    time.sleep(10) # pause, to see the memory usage in top
    print('added one')
    existing_shm.close()

def create_shared_block():

    a = np.ones(shape=(dim, dim), dtype=np.int64)  # Start with an existing NumPy array

    shm = shared_memory.SharedMemory(create=True, size=a.nbytes)
    # # Now create a NumPy array backed by shared memory
    np_array = np.ndarray(a.shape, dtype=np.int64, buffer=shm.buf)
    np_array[:] = a[:]  # Copy the original data into shared memory
    return shm, np_array

if current_process().name == "MainProcess":
    print("creating shared block")
    shr, np_array = create_shared_block()

    processes = []
    for i in range(cpu_count()):
        _process = Process(target=add_one, args=(shr.name,))
        processes.append(_process)
        _process.start()

    for _process in processes:
        _process.join()

    print("Final array")
    print(np_array[:10])
    print(np_array[10:])

    shr.close()
    shr.unlink()

請注意,由於 64 位整數,此代碼可能需要大約 1gb 的內存才能運行,因此請確保使用它時不會凍結系統。 ^_^

如果您想使用寫時復制功能並且您的數據是靜態的(在子進程中未更改) - 您應該讓 python 不要弄亂數據所在的內存塊。 您可以通過使用 C 或 C++ 結構(例如 stl)作為容器輕松實現這一點,並提供您自己的 Python 包裝器,當將創建 Python 級對象(如果有的話)時,該包裝器將使用指向數據內存(或可能復制數據內存)的指針. 所有這一切都可以通過幾乎 python 的簡單性和cython 的語法輕松完成

# pseudo cython
cdef class FooContainer:
   cdef char * data
   def __cinit__(self, char * foo_value):
       self.data = malloc(1024, sizeof(char))
       memcpy(self.data, foo_value, min(1024, len(foo_value)))
   
   def get(self):
       return self.data
# python part
from foo import FooContainer

f = FooContainer("hello world")
pid = fork()
if not pid:
   f.get() # this call will read same memory page to where
           # parent process wrote 1024 chars of self.data
           # and cython will automatically create a new python string
           # object from it and return to caller

上面的偽代碼寫得不好。 不要使用它。 在您的情況下,代替 self.data 應該是 C 或 C++ 容器。

對於那些有興趣使用 Python3.8 的shared_memory模塊的人,它仍然有一個尚未修復的錯誤,並且目前正在影響 Python3.8/3.9/3.10 (2021-01-15)。 該錯誤影響 posix 系統,並且與資源跟蹤器在其他進程仍應具有有效訪問權限時破壞共享內存段有關。 因此,如果您在代碼中使用它,請務必小心。

您可以使用 memcached 或 redis 並將每個設置為鍵值對 {'l1'...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM