在子进程已经开始之后访问共享内存

时间:2011-09-14 15:44:21

标签: python ipc multiprocessing shared-memory

如果数据仅在生成子进程后生成(使用multiprocessing.Process),如何让子进程访问共享内存中的数据?

我知道multiprocessing.sharedctypes.RawArray,但我无法弄清楚如何让我的子进程访问在进程已经启动后创建的RawArray

数据由父进程生成,预先不知道数据量。

如果不是GIL,我会使用线程代替,这将使这项任务更简单一些。使用非CPython实现不是一种选择。


muliprocessing.sharedctypes的引擎盖下,看起来共享的ctype对象被分配using mmaped memory

所以这个问题实际上归结为:子进程生成后,如果父进程调用了mmap(),子进程是否可以访问匿名映射的内存?

除了在我的情况下mmap()的调用者是父进程而不是子进程之外,这与this question中的问题有点相似。


(解决)

我创建了自己的RawArray版本,其中使用了shm_open()。只要标识符(tag)匹配,就可以与任何进程共享生成的共享ctypes数组。

有关详细信息和示例,请参阅this answer

3 个答案:

答案 0 :(得分:6)

您的问题听起来非常适合posix_ipc or sysv_ipc modules,它会为共享内存,信号量和消息队列公开POSIX或SysV API。其中的特征矩阵包含了在他提供的模块中挑选的极好建议。

匿名mmap(2)区域的问题在于您无法轻松地与其他进程共享它们 - 如果它们是文件支持的,那很容易,但如果您实际上不需要该文件的任何内容否则,感觉很傻。你可以使用CLONE_VM标志进行clone(2)系统调用,如果它在C中,但是我不想尝试将它与可能做出假设的语言解释器一起使用关于记忆安全。 (即使在C语言中它也有点危险,因为五年后的维护程序员可能会对CLONE_VM行为感到震惊。

但SysV和更新的POSIX共享内存映射允许甚至不相关的进程通过标识符附加和分离共享内存,因此您需要做的就是共享来自创建映射的进程的标识符与使用映射的进程,然后当您在映射中操作数据时,它们可同时用于所有进程,而无需任何额外的解析开销。 shm_open(3)函数返回int,在以后调用ftruncate(2)然后mmap(2)时用作文件描述符,因此其他进程可以使用没有文件的共享内存段在文件系统中创建 - 即使所有使用它的进程都已退出,该内存仍将保留。 (或许对Unix来说有点奇怪,但它很灵活。)

答案 1 :(得分:6)

免责声明:我是问题的作者。

我最终使用posix_ipc模块创建了自己的RawArray版本。我主要使用posix_ipc.SharedMemory来调用shm_open()

我的实现(ShmemRawArray)公开了与RawArray相同的功能,但需要两个额外的参数 - tag来唯一标识共享内存区域,以及create标志确定是否应该创建新的共享内存段或附加到现有的内存段。

如果有人感兴趣的话,这是一份副本:https://gist.github.com/1222327

ShmemRawArray(typecode_or_type, size_or_initializer, tag, create=True)

使用说明:

  • 前两个参数(typecode_or_typesize_or_initializer)的工作方式应与RawArray相同。
  • 只要tag匹配,任何进程都可以访问共享数组。
  • 当原始对象(由ShmemRawArray(..., create=True)返回)被删除时,共享内存段被取消链接
  • 使用当前存在的tag创建共享阵列将引发ExistentialError
  • 使用不存在的tag访问共享阵列(或已取消链接的共享阵列)也会引发ExistentialError

SSCCE(短,自包含,可编辑的示例)显示它的实际效果。

#!/usr/bin/env python2.7
import ctypes
import multiprocessing
from random import random, randint
from shmemctypes import ShmemRawArray

class Point(ctypes.Structure):
    _fields_ = [ ("x", ctypes.c_double), ("y", ctypes.c_double) ]

def worker(q):
    # get access to ctypes array shared by parent
    count, tag = q.get()
    shared_data = ShmemRawArray(Point, count, tag, False)

    proc_name = multiprocessing.current_process().name
    print proc_name, ["%.3f %.3f" % (d.x, d.y) for d in shared_data]

if __name__ == '__main__':
    procs = []
    np = multiprocessing.cpu_count()
    queue = multiprocessing.Queue()

    # spawn child processes
    for i in xrange(np):
        p = multiprocessing.Process(target=worker, args=(queue,))
        procs.append(p)
        p.start()

    # create a unique tag for shmem segment
    tag = "stack-overflow-%d" % multiprocessing.current_process().pid

    # random number of points with random data
    count = randint(3,10) 
    combined_data = [Point(x=random(), y=random()) for i in xrange(count)]

    # create ctypes array in shared memory using ShmemRawArray
    # - we won't be able to use multiprocssing.sharectypes.RawArray here 
    #   because children already spawned
    shared_data = ShmemRawArray(Point, combined_data, tag)

    # give children info needed to access ctypes array
    for p in procs:
        queue.put((count, tag))

    print "Parent", ["%.3f %.3f" % (d.x, d.y) for d in shared_data]
    for p in procs:
        p.join()

运行此结果会产生以下输出:

[me@home]$ ./shmem_test.py
Parent ['0.633 0.296', '0.559 0.008', '0.814 0.752', '0.842 0.110']
Process-1 ['0.633 0.296', '0.559 0.008', '0.814 0.752', '0.842 0.110']
Process-2 ['0.633 0.296', '0.559 0.008', '0.814 0.752', '0.842 0.110']
Process-3 ['0.633 0.296', '0.559 0.008', '0.814 0.752', '0.842 0.110']
Process-4 ['0.633 0.296', '0.559 0.008', '0.814 0.752', '0.842 0.110']

答案 2 :(得分:0)

我认为您正在寻找mmap module

关于数据的序列化this question当然如果您希望避免复制,请回答我没有解决方案

修改

实际上你可以使用CPython 3.2中的非stdlib _mutliprocessing模块来获取mmap对象的地址,并将其与ctypes对象的from_address一起使用 事实上,RawArray实际上是什么,当然你不应该尝试调整mmap对象的大小,因为在这种情况下mmap的地址可能会改变

import mmap
import _multiprocessing
from ctypes import Structure,c_int

map = mmap.mmap(-1,4)
class A(Structure):
    _fields_ = [("x", c_int)]
x = _multiprocessing.address_of_buffer(map)
b=A.from_address(x[0])
b.x = 256

>>> map[0:4]
'\x00\x01\x00\x00'

在创建子项后公开内存,您必须使用正在调用的实际文件映射内存

map = mmap.mmap(open("hello.txt", "r+b").fileno(),4)