【问题标题】:How to share objects and data between python processes in real-time?如何在python进程之间实时共享对象和数据?
【发布时间】:2014-10-24 18:58:13
【问题描述】:

我正在尝试在 Python 中为实时应用程序、多处理和大文件找到一种合理的方法。

父进程产生 2 个或更多子进程。第一个孩子读取数据,保存在内存中,其他孩子以管道方式处理它。数据应该组织成一个对象,发送到后面的进程,处理,发送,处理等等。

由于开销(序列化等),可用的方法,如管道、队列、管理器似乎不够用。

对此是否有适当的方法?

【问题讨论】:

  • 首先,如果没有一些分析数据,或者没有分析为什么您希望序列化比实际工作花费更长的时间,“似乎不够”就不是一个好的论据。只需提出一些好的样本数据、构建概念证明并进行测试,通常会更快、更容易。如果事实证明您只花费 2% 的时间在队列中腌制和发送内容,那么您一开始就没有问题。

标签: python multiprocessing


【解决方案1】:

我曾在高内存应用程序中使用 Celery 和 Redis 进行实时多处理,但这实际上取决于您要完成的工作。

我发现 Celery 相对于内置多处理工具(管道/队列)的最大好处是:

  • 低开销。直接调用函数,不需要序列化数据。
  • 缩放。需要增加工作进程?只需添加更多工人。
  • 透明度。易于检查任务/工作人员并发现瓶颈。

为了真正提高性能,ZMQ 是我的首选。设置和微调还有很多工作要做,但它尽可能接近裸插座。

免责声明:这都是轶事。这真的取决于您的具体需求。在你走任何路之前,我会用样本数据对不同的选项进行基准测试。

【讨论】:

  • 这些技术还在序列化数据;如果不通过网络传递参数,就无法通过网络获取函数的参数。他们只是强迫你不要以一种天真的方式去做,最终会腌制一大堆复杂的东西,并且通常在他们传递你的东西的方式上更有效率。好吧,“只是”有点夸张。两者在实践中都有很大的好处,但你知道我的意思。
【解决方案2】:

首先,由于所有开销而怀疑消息传递可能不充分并不是使您的程序过于复杂的好理由。这是建立概念证明并提供一些示例数据并开始测试的一个很好的理由。如果您花费 80% 的时间来腌制东西或通过队列推送东西,那么是的,这可能会成为您现实生活中的代码中的一个问题——假设您的概念证明所做的工作量与您的真实代码相当代码。但是如果你把 98% 的时间都花在了真正的工作上,那么就没有问题需要解决了。消息传递会更简单,所以就用它吧。

此外,即使您确实在这里发现了问题,这并不意味着您必须放弃消息传递;这可能只是multiprocessing 内置的问题。 0MQ 和 Celery 等技术的开销可能比简单队列低。即使对通过队列发送的内容更加小心也会产生巨大的影响。


但如果消息传递不可行,显而易见的替代方案是数据共享。 multiprocessing 文档中很好地解释了这一点,以及每个文档的优缺点。

Sharing state between processes 描述了如何做到这一点的基础知识。还有其他替代方案,例如使用特定于平台的共享内存 API 的 mmapped 文件,但没有太多理由在 multiprocessing 上这样做,除非您需要,例如运行之间的持久存储。

有两个大问题要处理,但都可以处理。

首先,您不能共享 Python 对象,只能共享简单的值。 Python 对象到处都有相互的内部引用,垃圾收集器看不到对其他进程堆中对象的引用,等等。所以multiprocessing.Value 只能保存与array.array 相同的基本类型的本机值,而multiprocessing.Array 可以保存(正如您从名称中猜到的)相同值的一维数组,仅此而已。对于更复杂的事情,如果你可以用ctypes.Structure 来定义它,你可以使用https://docs.python.org/3/library/multiprocessing.html#module-multiprocessing.sharedctypes,但这仍然意味着对象之间的任何引用都必须是间接的。 (例如,您经常必须将索引存储到数组中。)(当然,如果您使用 NumPy,这些都不是坏消息,因为您可能已经将大部分数据存储在简单值的 NumPy 数组中,这是可共享的。)

其次,共享数据当然会受到竞争条件的影响。而且,与单个进程中的多线程不同,您不能依赖 GIL 来帮助保护您;有多个解释器都可以同时尝试修改相同的数据。所以你必须使用锁或条件来保护东西。

【讨论】:

  • 我想与子进程共享父进程的 numpy 随机状态。我试过使用Manager,但仍然没有运气。你能看看我的问题here,看看你能不能提供一个解决方案?如果我每次生成随机数时都执行np.random.seed(None),我仍然可以获得不同的随机数,但这不允许我使用父进程的随机状态,这不是我想要的。非常感谢任何帮助。
【解决方案3】:

对于多处理管道,请查看MPipe

对于共享内存(特别是 NumPy 数组),请查看 numpy-sharedmem

我使用这些来执行高性能实时、并行图像处理(使用 OpenCV 进行平均累积和人脸检测),同时从多核 CPU 系统中挤出所有可用资源。如果有兴趣,请查看Sherlock。希望这会有所帮助。

【讨论】:

    【解决方案4】:

    一种选择是使用类似brain-plasma 的东西,它维护一个独立于 Python 进程或线程的共享内存对象命名空间。有点像 Redis,但可以与大对象一起使用,并且有一个简单的 API,构建在 Apache Arrow 之上。

    $ pip install brain-plasma
    
    # process 1
    from brain_plasma import Brain
    brain = Brain()
    brain['myvar'] = 657
    
    # process 2
    from brain_plasma import Brain
    brain = Brain()
    brain['myvar']
    # >>> 657
    

    【讨论】:

      【解决方案5】:

      Python 3.8 现在使用multiprocessing.shared_memory 在进程之间提供共享内存访问。您在进程之间传递的只是一个引用共享内存块的字符串。在消费过程中,您会得到一个memoryview 对象,该对象支持切片,而无需像字节数组那样复制数据。如果您使用 numpy,它可以在 O(1) 操作中引用内存块,从而允许快速传输大块数字数据。据我了解,通用对象仍然需要反序列化,因为原始字节数组是消费进程接收到的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-11-28
        • 2012-07-22
        • 2017-04-22
        • 2019-02-05
        • 1970-01-01
        • 1970-01-01
        • 2017-02-02
        • 1970-01-01
        相关资源
        最近更新 更多