Python的multiprocessing与threading.local不兼容?

12 投票
3 回答
3212 浏览
提问于 2025-04-17 01:15

我有两个进程(见下面的示例代码),它们都试图访问一个叫做 threading.local 的对象。我本以为下面的代码会打印出 "a" 和 "b"(顺序可以是任意的)。结果却打印出了 "a" 和 "a"。我该如何优雅且稳妥地在启动全新的进程时重置这个 threading.local 对象呢?

import threading
import multiprocessing
l = threading.local()
l.x = 'a'
def f():
    print getattr(l, 'x', 'b')
multiprocessing.Process(target=f).start()
f()

补充说明:作为参考,当我使用 threading.Thread 而不是 multiprocessing.Process 时,结果是如我所预期的那样。

3 个回答

3

现在有一个叫做 multiprocessing-utils 的库,可以在 pypi 上找到,里面有一个安全的版本的 threading.local(),可以通过 pip 安装。

这个库的工作原理是,它会把标准的 threading.local() 包装起来,并检查自上次使用以来进程ID(PID)是否没有改变(具体可以参考 这里 @immortal 的回答)。

使用方法和 threading.local() 一模一样:

l = multiprocessing_utils.local()
l.x = 'a'
def f():
    print getattr(l, 'x', 'b')
f()                                        # prints "a"
threading.Thread(target=f).start()         # prints "b"
multiprocessing.Process(target=f).start()  # prints "b"

需要说明的是:这个模块是我刚刚创建的。

4

因为 threading.local 这个东西是专门为线程设计的,而不是为进程准备的,这在它的 文档 中说得很清楚:

每个线程的实例值都是不同的。

这里面没有提到进程。

还有来自多进程 文档 的一句话:

注意

多进程模块里没有类似于 threading.active_count()、threading.enumerate()、threading.settrace()、threading.setprofile()、threading.Timer,或者 threading.local 的东西。

9

你提到的这两个操作系统都是基于Unix/Linux的,所以它们使用的是相同的fork()接口。fork()这个操作会完全复制一个进程,包括它的内存、加载的代码、打开的文件描述符和线程。而且,新创建的进程通常会在内核中共享同一个进程对象,直到第一次写入内存。这基本上意味着,局部数据结构也会被复制到新进程中,包括线程局部变量。因此,你仍然拥有相同的数据结构,l.x依然是定义好的。

为了重置新进程的数据结构,我建议在进程启动的函数中,先调用一些清理的方法。比如,你可以用process_id = os.getpid()来存储父进程的进程ID,然后在

if process_id != os.getpid(): 
   clear_local_data()

的子进程主函数中使用它。

撰写回答