我在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的版本也不是那么容易,尽管我可以让集群管理员在需要时构建更新的版本。
这可能是问题吗?
答案 0 :(得分: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让你意味着两个不同的东西在一个包含在一个包中的模块中说import foo
:你可能正在尝试相对导入是the_package.foo
,或者您可能正在尝试导入顶级包foo
。有关为何在最近的Python版本中更改这种模糊且昂贵的行为的详细信息,请参阅PEP 328:
http://legacy.python.org/dev/peps/pep-0328/
在此背景下,您可以查看strace
输出,并在此处注意答案中尚未提及的内容:列出的stat()
和open()
系统调用为不尝试导入模块random
,而是导入名为multiprocessing.random
的不存在的模块!
即使random
中已列出sys.modules
,这也是尝试附加导入的关键原因 - 因为在允许Python 2.6回归之前假设import
语句实际上旨在导入random
,它必须消除它尝试相对导入multiprocessing.random
的可能性,因为import
语句出现在multiprocessing.forking
子模块的代码。
程序员应该说sys.modules['random'].seed()
而不是尝试新的导入来为你节省额外的系统调用。但是,一旦你有机会升级到更新版本的Python,希望你不会因为这种行为而感到困扰。
答案 1 :(得分:1)
这是python导入模块时的作用。没有什么问题。在第一次访问之后,事情将在文件系统缓存中,因此这不太可能导致任何问题。
Python检查PYTHONPATH中的所有文件夹,查找具有给定名称的模块可能具有的所有有效名称。当您运行使用动态库的编译程序时会发生类似的事情 - 动态链接器也将搜索库的各个位置,直到找到它为止。
答案 2 :(得分: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中,那么它确实会随机导入并使用它来生成新的随机种子。我想对某些应用程序自动完成这项工作可能会很好,但我当然不会期望它。也许有充分的理由这样做,但我不需要它。
由于我的多处理需求非常简单,我现在只是自己做分叉:
childpid = os.fork()
if childpid == 0:
...run code...
os._exit(0)
else:
os.waitpid(childpid, 0)
当然这没有导入,所以我没有搜索任何东西。通过继承多处理的适当位并仅删除'import',也可以使搜索消失。我不知道为什么我的笔记本电脑上没有发生搜索,因为我运行的是同一版本的多处理。
答案 3 :(得分:0)
您的操作系统是什么?我猜它是Windows。正如ThiefMaster所说,行为是正常的,但是你在每次循环迭代中得到它的原因可能是因为Windows上的multiprocessing imports the __main__
module。尝试保护if __name__=="__main__"
块内的循环。