3

我在 Python 中观察到一些极端奇怪的行为。考虑以下代码:

from multiprocessing import Process  
import scipy

def test():
    pass

for i in range(1000):
    p1 = Process(target=test)
    p1.start()
    p1.join()
    print i

当我对此运行 strace -f 时,我从循环中得到以下段:

clone(Process 19706 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x2b23afde1970) = 19706
[pid 19706] set_robust_list(0x2b23afde1980, 0x18) = 0
[pid 18673] wait4(19706, Process 18673 suspended
 <unfinished ...>
[pid 19706] stat("/apps/python/2.7.1/lib/python2.7/multiprocessing/random", 0x7fff041fc150) = -1 ENOENT (No such file or directory)
[pid 19706] open("/apps/python/2.7.1/lib/python2.7/multiprocessing/random.so", O_RDONLY) = -1 ENOENT (No such file or directory)
[pid 19706] open("/apps/python/2.7.1/lib/python2.7/multiprocessing/randommodule.so", O_RDONLY) = -1 ENOENT (No such file or directory)
[pid 19706] open("/apps/python/2.7.1/lib/python2.7/multiprocessing/random.py", O_RDONLY) = -1 ENOENT (No such file or directory)
[pid 19706] open("/apps/python/2.7.1/lib/python2.7/multiprocessing/random.pyc", O_RDONLY) = -1 ENOENT (No such file or directory)
[pid 19706] open("/dev/urandom", O_RDONLY) = 3
[pid 19706] read(3, "\3\204g\362\260\324:]\337F0n\n\377\317\343", 16) = 16
[pid 19706] close(3)                    = 0
[pid 19706] open("/dev/null", O_RDONLY) = 3
[pid 19706] fstat(3, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), ...}) = 0
[pid 19706] exit_group(0)               = ?
Process 18673 resumed
Process 19706 detached

关于在文件系统中搜索“随机”的所有垃圾是怎么回事?我真的想避免这种情况,因为我在集群上并行运行了很多具有这种结构的进程,并且循环非常快,并且这种文件系统活动阻塞了文件系统元数据服务器,或者集群管理员告诉我.

如果我删除“import scipy”命令,那么这个问题就会消失:

clone(Process 23081 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x2b42ec15e970) = 23081
[pid 23081] set_robust_list(0x2b42ec15e980, 0x18) = 0
[pid 22052] wait4(23081, Process 22052 suspended
 <unfinished ...>
[pid 23081] open("/dev/null", O_RDONLY) = 3
[pid 23081] fstat(3, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), ...}) = 0
[pid 23081] exit_group(0)               = ?
Process 22052 resumed
Process 23081 detached

但我需要在我的真实代码中使用 scipy,所以我不能只是摆脱它。或者也许我可以,但那会很痛苦。

有谁知道我为什么会看到这种行为?如果它是某些版本的怪癖,我正在运行以下命令:

python:2.7.1,多处理:0.70a1,scipy:0.9.0,

实际上,因为我刚刚意识到它可能取决于系统,所以我在笔记本电脑上运行了相同的代码并且没有问题(即输出相当于第二种情况)。在我正在运行的笔记本电脑上

python:2.6.5,多处理:0.70a1,scipy:0.10.0,

也许这是已修复的早期版本的 scipy 中的问题或错误?我对这样的任何东西的搜索都没有出现。即使这是问题所在,在集群上更改 scipy 的版本也不是那么容易,尽管如果需要,我可能可以让集群管理员构建更新的版本。

这可能是问题吗?

4

4 回答 4

5

这不是因为 Windows 或__main__模块。这也不是 Python 喜欢做生意的方式。而且,如果您重新检查,我想您会发现这是 Python 2.6 而不是 2.7 的行为,除非您运行的是修改后的 2.7。

您完全正确,该问题源于模块中的随机模块初始化步骤multiprocessing.forking——该步骤旨在防止您的进程在分叉产生n 个工作人员时创建所有通过完全相同的一系列伪程序向前迈进的工作人员随机数(例如,如果它们都使用这些数字协商 SSL 连接,则可能会危及安全性):

        if 'random' in sys.modules:
            import random
            random.seed()

但这里的关键是要认识到,从系统调用的角度来看,上述import语句应该是无操作的,因为如果模块名称已经作为键存在于sys.modules字典中,那么import只需返回它在其中找到的值无需尝试从文件系统加载任何内容:

>>> import sys
>>> sys.modules['fake'] = 'Not even a module'
>>> import fake
>>> fake
'Not even a module'

if因此,上面引用的声明专门试图防止在模块甚至还没有加载import的情况下产生额外的费用。random当您在没有scipy加载的情况下进行实验时,if语句体甚至不会触发。

那么问题是什么?

问题是 2.7 之前的旧版本 Python 让您通过在位于包内的模块中说两个不同的东西:您可能正在尝试相对导入,或者您可能正在尝试导入顶级包. 请参阅 PEP 328,详细了解为什么这种模棱两可且代价高昂的行为现在已在较新版本的 Python 中被更改:import foothe_package.foofoo

http://legacy.python.org/dev/peps/pep-0328/

在此背景下,您可以查看您的strace输出并注意此处的答案中尚未提及的内容:列出的stat()和系统调用不是尝试导入模块,而是不存在的模块名为!open()randommultiprocessing.random

这是尝试额外导入的关键原因,即使random已经列出sys.modules- 因为在 Python 2.6 允许回退到该import语句真正旨在导入的假设之前random,它必须消除它相反的可能性尝试相对导入,multiprocessing.random因为该import语句出现在multiprocessing.forking子模块的代码中。

程序员真的应该说sys.modules['random'].seed()而不是尝试新的导入来省去那些额外的系统调用。但希望一旦您有机会升级到更新版本的 Python,您不会被这种行为长期困扰。

于 2014-07-24T16:52:05.593 回答
1

这就是 python 在导入模块时所做的事情。没有什么问题。在第一次访问之后,无论如何都会在文件系统缓存中,因此这不太可能导致任何问题。

Python 检查 PYTHONPATH 中的所有文件夹以查找具有给定名称的模块可能具有的所有有效名称。当您运行使用动态库的已编译程序时,也会发生类似的事情 - 动态链接器还将搜索库的各个位置,直到找到它。

于 2012-06-11T02:07:17.603 回答
1

好的,看起来 ThiefMaster 是完全正确的,没有任何问题,尽管我仍然不喜欢它并且会避免它。但首先,这就是正在发生的事情。在 multiprocessing.forking 中会发生以下情况:

class Popen(object):

    def __init__(self, process_obj):
        sys.stdout.flush()
        sys.stderr.flush()
        self.returncode = None

        self.pid = os.fork()
        if self.pid == 0:
            if 'random' in sys.modules:
                import random
                random.seed()
            code = process_obj._bootstrap()
            sys.stdout.flush()
            sys.stderr.flush()
            os._exit(code)

因此,如果 'random' 在 sys.modules 中,那么它确实会导入 random 并使用它来生成新的随机种子。我想某些应用程序自动完成此操作可能会很好,但我当然不会想到它。也许这样做有充分的理由,但我不需要这样做。

由于我的多处理需求非常简单,我现在自己做叉子:

    childpid = os.fork()
    if childpid == 0:
        ...run code...
        os._exit(0)
    else:
        os.waitpid(childpid, 0)

当然这不会导入,所以我没有搜索任何东西。也可以通过对多处理的适当位进行子类化并仅删除“导入”来使搜索消失。我不知道为什么我的笔记本电脑上没有进行搜索,因为我正在运行相同版本的多处理。

于 2012-06-12T03:35:23.233 回答
0

你的操作系统是什么?我猜是Windows。正如 ThiefMaster 所指出的,这种行为是正常的,但您在每次循环迭代中都得到它的原因可能是因为multiprocessing__main__在 Windows 上导入了模块。尝试在一个if __name__=="__main__"块内保护你的循环。

于 2012-06-11T05:25:38.673 回答