2016-11-29 21 views
1

我必须从许多进程访问一组较大且不可选择的python对象。因此,我想确保这些对象不被完全复制。在Linux系统上检查python多处理中的fork行为

根据在thisthis后的注释,对象不会被复制(在unix系统上),除非它们被更改。但是,引用一个对象将改变其引用计数,然后将被复制。

到目前为止这是否正确?由于我的关注是由于我的大型对象的大小,我没有问题,如果这些对象的小部分被复制。

为了确保我理解正确的一切,没有什么意外的话,我实现了一个小的测试程序:

from multiprocessing import Pool 

def f(arg): 
    print(l, id(l), object.__repr__(l)) 
    l[arg] = -1 
    print(l, id(l), object.__repr__(l)) 

def test(n): 
    global l 
    l = list(range(n)) 
    with Pool() as pool: 
     pool.map(f, range(n)) 
    print(l, id(l), object.__repr__(l)) 

if __name__ == '__main__': 
    test(5) 

f第一线,我希望id(l)在所有函数返回相同的数来电,因为在id检查前列表没有被更改。

另一方面,在f的第三行中,id(l)应在每个方法调用中返回不同的数字,因为列表在第二行中更改。

但是,程序输出令我困惑。

[0, 1, 2, 3, 4] 139778408436488 <list object at 0x7f20b261d308> 
[-1, 1, 2, 3, 4] 139778408436488 <list object at 0x7f20b261d308> 
[0, 1, 2, 3, 4] 139778408436488 <list object at 0x7f20b261d308> 
[0, -1, 2, 3, 4] 139778408436488 <list object at 0x7f20b261d308> 
[0, 1, 2, 3, 4] 139778408436488 <list object at 0x7f20b261d308> 
[0, 1, -1, 3, 4] 139778408436488 <list object at 0x7f20b261d308> 
[0, 1, 2, 3, 4] 139778408436488 <list object at 0x7f20b261d308> 
[0, 1, 2, -1, 4] 139778408436488 <list object at 0x7f20b261d308> 
[0, 1, 2, 3, 4] 139778408436488 <list object at 0x7f20b261d308> 
[0, 1, 2, 3, -1] 139778408436488 <list object at 0x7f20b261d308> 
[0, 1, 2, 3, 4] 139778408436488 

id是在所有通话和f线相同。即使列表在最后保持不变(如预期),情况也是如此,这意味着列表已经被复制了

如何查看对象是否已被复制?

+0

一旦对象被创建没有任何东西可以改变它的ID。 –

+0

@RossRidge:当我复制对象,岂不是在比原来的内存不同的地方?有没有办法获得这个内存地址(如果'ID'返回别的东西)? – Samufi

+0

谢谢,@JonathanEunice。我试图纠正我的陈述。现在好吗? – Samufi

回答

2

您的困惑似乎是由于误解过程和工作方式而导致的。每个进程都有自己的地址空间,因此两个进程可以使用相同的地址而不会发生冲突。这也意味着一个进程无法访问另一个进程的内存,除非相同的内存映射到两个进程中。

当进程调用系统调用时,操作系统创建一个新的子进程,该进程是父进程的克隆。和其他进程一样,这个克隆拥有与父进程不同的独立地址空间。但是,地址空间的内容是父级的精确副本。这通常是通过将父进程的内存复制到为该子进程分配的新内存中来完成的。这意味着一旦孩子和父母恢复执行任何修改后,任何修改过程都会使自己的记忆不会影响其他记忆。

但是,复制进程的整个地址空间是一项昂贵的操作,通常是浪费。大多数情况下,新进程立即执行一个新程序,导致孩子的地址空间被完全替换。因此,现代的类Unix操作系统使用“写入时拷贝”实现。不是复制父进程的内存,而是将父进程的内存映射到子进程中,以便它们可以共享相同的内存。但是,旧的语义仍然保留。如果孩子或父母修改共享内存,则复制的页面将被复制,以便两个进程不再共享该页内存。

multiprocessing模块调用您的f函数时,它在使用系统调用fork创建的子进程中执行此操作。由于该子进程是父进程的一个克隆,它也有一个名为l一个全局变量是指具有相同的ID(地址)和相同的内容在这两个进程的列表。也就是说,直到您修改子进程中由l引用的列表。该ID不(并且不能)改变,但是该孩子的列表版本不再与父母相同。家长列表的内容不会影响孩子的修改。

注意,在上一段中描述的行为是真实的fork是否使用写入时复制或没有。至于multiprocessing模块和Python普遍担心,这只是一个实现细节。无论如何,有效的结果都是一样的。这意味着你不能真正在使用fork执行Python程序测试。

相关问题