【问题标题】:`multiprocessing.Process` are modifying non-shared variables they should not have access to`multiprocessing.Process` 正在修改他们不应访问的非共享变量
【发布时间】:2021-08-30 15:43:01
【问题描述】:

进程正在改变它们不应该改变的东西。

Worker 有一个状态变量(mp.Value)。此值设置为-1,它(Worker)在循环中将其更改为1

但是,似乎可以通过生成第二个 Worker 将该值 back 重置为 -1,尽管这与原始对没有任何共同之处。这似乎是不可能的。

行为:

当第二个 Worker 启动时,第一个 worker (self.state.value) 的状态被重置为 -1。这被捕获,我们打印出发现了一个错误。

代码:

import multiprocessing as mp
import time


class Worker:
    def __init__(self, tag, service_state) -> None:
        self.tag = tag
        self.local_state = int(service_state.value)
        self.state = service_state
        self.run_work_loop()

    def run_work_loop(self) -> None:
        print(f"[{self.tag}] Running... {self.state.value} {self.local_state}")
        while True:
            if self.state.value != self.local_state:
                print(f"[{self.tag}] Illegal change. Shared state: {self.state.value} Local State: {self.local_state}")
                break

            elif self.state.value == -1:
                self.state.value = self.local_state = 1
                print(f"[{self.tag}] Set Shared State: {self.state.value} Local State: {self.local_state}.")


if __name__ == "__main__":
    mp.Process(target=Worker, args=("A", mp.Value('i', -1))).start()
    time.sleep(.03)
    mp.Process(target=Worker, args=("B", mp.Value('i', -1))).start()


输出:

[A] Running... -1 -1
[A] Set Shared State: 1 Local State: 1.
[A] Illegal change. Shared state: -1 Local State: 1
[B] Running... -1 -1
[B] Set Shared State: 1 Local State: 1.

【问题讨论】:

  • 我怀疑问题与未在主进程中保留对 Values 的引用有关。无论如何,这完全违背了使用共享对象的目的。
  • @jasonharper 正确 - 如果将第一个 mp.Value 保存到局部变量,并将其传递给 args,问题就会消失。不完全确定为什么,但我认为 mp.Value 实例在直接传递到 args 时最终指向相同的东西。
  • 我认为这与分配multiprocessing.Value 的方式有关。在内部,它使用ctypesValue 分配内存。我怀疑是因为当您将mp.Value 直接传递给args 时,它超出了父进程的范围,当您创建第二个Value 时,相同的内存位置会被重用。

标签: python multiprocessing python-multiprocessing


【解决方案1】:

问题是您正在创建的 Value 实例立即超出父进程的范围,这会使它们被垃圾收集。因为of the way Python allocates memory for multiprocessing.Value objects,第二个Value 最终使用与第一个Value 完全相同的共享内存位置,这意味着第二个最终会踩到第一个。你可以做一些实验来看看这个效果。例如,这不会打印警告:

if __name__ == "__main__":
    mp.Process(target=Worker, args=("A", mp.Value('i', -1))).start()
    time.sleep(.03)
    a = mp.Value('i', 1)
    mp.Process(target=Worker, args=("B", mp.Value('i', -1))).start()

我们分配给aValue 被初始化为1,这会覆盖我们传递给进程“A”的匿名Value。因为我们用 1 覆盖它,所以不会打印非法状态消息。如果我们改为将其初始化为任何其他值,您将再次看到警告。这将打印关于 -2 的非法状态消息,例如:

if __name__ == "__main__":
    mp.Process(target=Worker, args=("A", mp.Value('i', -1))).start()
    time.sleep(.03)
    a = mp.Value('i', -2)
    mp.Process(target=Worker, args=("B", mp.Value('i', -1))).start()

您的代码应该真正将您创建的Value 实例保存为父进程中的局部变量,既可以避免这个问题,又因为创建您实际上不共享的共享值是没有意义的。像这样:

if __name__ == "__main__":
    a = mp.Value('i', -1)
    mp.Process(target=Worker, args=("A", a)).start()
    time.sleep(.03)
    b = mp.Value('i', -1)
    mp.Process(target=Worker, args=("B", b)).start()

【讨论】:

  • 您可以通过在文件顶部添加import ctypes 并在调用self.run_work_loop() 之前添加print(f"[{self.tag}] Address of service_state storage: {ctypes.addressof(service_state._obj):#x}") 来验证这实际上是问题所在。两个子进程都报告说它们独立构造的mp.Values 的底层存储在同一位置。检查源代码,Process.start 故意 dels targetargskwargs 在子进程生成之后、start 返回之前提供; refcnt 不是跨进程的,所以它被清理了。
  • 我认为更安全的解决方案是保留父级中的 mp.Values (v1, v2 = mp.Value('i', -1), mp.Value('i', -1),然后将 v1 传递给第一个 Processv2 到第二个,而不是伪造mp.Values 只是为了回收内存(不能保证顺序分配/解除分配/分配操作实际上会两次获得相同的内存,但如果你坚持使用内存,它肯定不会被重用)。让共享的mp.Values 毕竟实际上没有共享是相当奇怪的。
  • @ShadowRanger 代码示例并不是一个解决方案,它只是演示正在发生的事情。当然,正确的做法是将值保存到局部变量中!我将更新我的答案以明确说明
猜你喜欢
  • 1970-01-01
  • 2021-08-12
  • 1970-01-01
  • 2018-01-08
  • 1970-01-01
  • 2013-10-29
  • 2011-09-08
  • 1970-01-01
  • 2018-08-15
相关资源
最近更新 更多