Python的多进程与threading.local不兼容?

12

我有两个进程(见样例代码),每个进程都试图访问一个线程本地对象。我希望下面的代码打印出"a"和"b"(以任何顺序)。但实际上,我得到的是"a"和"a"。当我启动全新的进程时,我该如何优雅且可靠地重置线程本地对象?

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 时,它按预期工作。


如果您能指定运行此程序的操作系统,那将非常有帮助... - immortal
目前在 OS X 上运行,但也已在 Fedora 上进行测试。 - dave mankoff
3个回答

9

你提到的这两个操作系统都是基于Unix / Linux,因此实现了相同的fork() API。fork()完全复制进程对象,包括其内存、加载的代码、打开的文件描述符和线程。此外,新进程通常与内核中的同一进程对象共享,直到第一个内存写入操作为止。这基本上意味着本地数据结构也将被复制到新进程中,包括线程局部变量。因此,你仍然拥有相同的数据结构和l.x仍然被定义。

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

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

在子进程的主函数中。

但是线程标识符也被复制了吗?在那一点上它们是不同的线程;threading.local 不应该处理这个吗? - dave mankoff
@dave 这怎么可能呢?fork() 的整个目的就是允许子进程继续运行,而不知道它被克隆了。唯一的区别是 fork() 自身的返回值:对于子进程是0,对于父进程是子进程的进程标识符。 - immortal
嗯,我想它可以使用类似于os.getpid()的东西来识别它已经移动到另一个线程了。也许这会成为一个不错的功能请求。 - dave mankoff
@dave 请注意,os.getpid() 是一个进程标识符,而不是线程标识符。如果您的进程运行多个线程,这将导致错误的线程标识符!(它们都将具有相同的pid) - immortal

4

因为 threading.local 仅适用于线程,而不适用于进程,正如其文档中清楚描述的那样:

该实例的值对于不同的线程将是不同的。

没有提到进程。

并且来自 multiprocessing 文档 的一句话:

注意

multiprocessing 不包含与 threading.active_count()、threading.enumerate()、threading.settrace()、threading.setprofile()、threading.Timer 或 threading.local 相似的内容。


这并不意味着子进程的唯一线程必须保留父进程调用线程的threading.local()对象的内容。 - Jacek Konieczny
但是当我启动一个新进程时,从技术上讲,这也是一个新线程,对吧?这正是我所期望的。 - dave mankoff
每个进程都将执行相同的模块级代码。因此,所有进程都将设置值为“a”。 - andrew cooke
@dave 这取决于您对“技术上”的理解。因此,在Python中创建新的“进程”时,它的代码将在新线程中在新进程中执行。 - Roman Bodnarchuk
@andrew cooke:这似乎不是这种情况。如果你用os.getpid()替换'a',它们都会打印出相同的整数。 - dave mankoff
显示剩余2条评论

3
现在在pypi上有一个名为multiprocessing-utilsgithub)的库,其中包含一个可以使用pip安装的多进程安全版本的threading.local()
它通过包装标准的threading.local()并检查PID是否自上次使用以来发生了变化(根据@immortal的回答here)来实现。
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"

全面披露:我刚刚创建了这个模块。

我大约6年前创建了一个解决这个问题的东西。当然,与你的产品相比,它的功能不是很丰富。https://github.com/mankyd/plocal https://pypi.python.org/pypi/plocal/ - dave mankoff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接