通过fork在多处理中收集共享数据的垃圾
garbage collection of shared data in multiprocessing via fork
我正在 linux 中进行一些多处理,我正在使用当前未显式传递给 child 进程(不是通过参数)的共享内存。
在官方 python 多处理 Programming guidelines 的 "Explicitly pass resources to child processes" 部分是这样写的:
On Unix using the fork start method, a child process can make use of a
shared resource created in a parent process using a global resource.
However, it is better to pass the object as an argument to the
constructor for the child process.... this ... ensures that as long as the
child process is still alive the object will not be garbage collected
in the parent process. This might be important if some resource is freed
when the object is garbage collected in the parent process.
这个解释我觉得有点欠缺。
- 我什么时候应该担心垃圾收集?
- 我是否应该始终将数据传递给 child,否则有时会出现意外结果,或者这只是最佳做法吗?
现在我没有遇到任何意外的垃圾收集,但是这种情况对我来说似乎不稳定。
这在很大程度上取决于 A) 您的数据 和 B) 您的多进程 method.
TLDR:
spawn
object 被克隆并且每个都被最终确定 在每个进程中
fork
/forkserver
object在主进程共享完成
一些 object 对在主进程中完成但仍在 child 进程中使用的响应很差。
args
上的文档是错误的,因为 args
的内容不会自行保存 (3.7.0)
注:Full code available as gist。 macOS 10.13.
上 CPython 3.7.0 的所有输出
我们从一个简单的 object 开始,报告最终确定的时间和地点:
def print_pid(*args, **kwargs): # Process aware print helper
print('[%s]' % os.getpid(), *args, **kwargs)
class Finalisable:
def __init__(self, name):
self.name = name
def __repr__(self):
return '<Finalisable object %s at 0x%x>' % (getattr(self, 'name', 'unknown'), id(self))
def __del__(self):
print_pid('finalising', self)
早 collection 来自 args
为了测试 args
对 GC 的工作原理,我们可以构建一个进程并立即释放其参数引用:
def drop_early():
payload = Finalisable()
child = multiprocessing.Process(target=print, args=(payload,))
print('drop')
del payload # remove sole local reference for `args` content
print('start')
child.start()
child.join()
使用 spawn
方法,收集了原件,但 child 有自己的副本要完成:
### test drop_early in 15333 method: spawn
drop
start
[15333] finalising <Finalisable object early at 0x102347390>
[15336] child sees <Finalisable object early at 0x109bd8128>
[15336] finalising <Finalisable object early at 0x109bd8128>
### done
用fork
方法,原件定型,child收到这个定型object:
### test drop_early in 15329 method: fork
drop
start
[15329] finalising <Finalisable object early at 0x108b453c8>
[15331] child sees <Finalisable object early at 0x108b453c8>
### done
这表明主进程的有效载荷在 child 进程运行并完成之前完成! 归根结底,args
不是防早collection!
早 collection 共享 objects
Python 有 some types meant for safe sharing between processes。我们也可以使用它作为我们的标记:
def drop_early_shared():
payload = Finalisable(multiprocessing.Value('i', 65))
child = multiprocessing.Process(target=print_pid, args=('child sees', payload,))
print('drop')
del payload
print('start')
child.start()
child.join()
使用 fork
方法,Value
被提早收集但仍然有效:
### test drop_early_shared in 15516 method: fork
drop
start
[15516] finalising <Finalisable object <Synchronized wrapper for c_int(65)> at 0x1071a3e10>
[15519] child sees <Finalisable object <Synchronized wrapper for c_int(65)> at 0x1071a3e10>
### done
使用 spawn
方法,Value
被提早收集并且完全破坏了 child:
### test drop_early_shared in 15520 method: spawn
drop
start
[15520] finalising <Finalisable object <Synchronized wrapper for c_int(65)> at 0x103a16c18>
[15524] finalising <Finalisable object unknown at 0x101aa0128>
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/spawn.py", line 105, in spawn_main
exitcode = _main(fd)
File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/spawn.py", line 115, in _main
self = reduction.pickle.load(from_parent)
File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/synchronize.py", line 111, in __setstate__
self._semlock = _multiprocessing.SemLock._rebuild(*state)
FileNotFoundError: [Errno 2] No such file or directory
### done
这表明最终化行为取决于您的 object 和您的环境。 底线,不要假设你的 object 是 well-behaved!
虽然通过 args
传递数据是一种很好的做法,但这 不会 使主进程免于处理它! Objects 当主进程删除引用时,可能会对提前完成做出糟糕的响应。
由于 CPython 使用 fast-acting 引用计数,您几乎可以立即看到不良影响。然而,其他实现,例如PyPy,可以在任意时间隐藏这样的 side-effects。
我正在 linux 中进行一些多处理,我正在使用当前未显式传递给 child 进程(不是通过参数)的共享内存。
在官方 python 多处理 Programming guidelines 的 "Explicitly pass resources to child processes" 部分是这样写的:
On Unix using the fork start method, a child process can make use of a shared resource created in a parent process using a global resource. However, it is better to pass the object as an argument to the constructor for the child process.... this ... ensures that as long as the child process is still alive the object will not be garbage collected in the parent process. This might be important if some resource is freed when the object is garbage collected in the parent process.
这个解释我觉得有点欠缺。
- 我什么时候应该担心垃圾收集?
- 我是否应该始终将数据传递给 child,否则有时会出现意外结果,或者这只是最佳做法吗?
现在我没有遇到任何意外的垃圾收集,但是这种情况对我来说似乎不稳定。
这在很大程度上取决于 A) 您的数据 和 B) 您的多进程 method.
TLDR:
spawn
object 被克隆并且每个都被最终确定 在每个进程中fork
/forkserver
object在主进程共享完成一些 object 对在主进程中完成但仍在 child 进程中使用的响应很差。
args
上的文档是错误的,因为args
的内容不会自行保存 (3.7.0)
注:Full code available as gist。 macOS 10.13.
上 CPython 3.7.0 的所有输出我们从一个简单的 object 开始,报告最终确定的时间和地点:
def print_pid(*args, **kwargs): # Process aware print helper
print('[%s]' % os.getpid(), *args, **kwargs)
class Finalisable:
def __init__(self, name):
self.name = name
def __repr__(self):
return '<Finalisable object %s at 0x%x>' % (getattr(self, 'name', 'unknown'), id(self))
def __del__(self):
print_pid('finalising', self)
早 collection 来自 args
为了测试 args
对 GC 的工作原理,我们可以构建一个进程并立即释放其参数引用:
def drop_early():
payload = Finalisable()
child = multiprocessing.Process(target=print, args=(payload,))
print('drop')
del payload # remove sole local reference for `args` content
print('start')
child.start()
child.join()
使用 spawn
方法,收集了原件,但 child 有自己的副本要完成:
### test drop_early in 15333 method: spawn
drop
start
[15333] finalising <Finalisable object early at 0x102347390>
[15336] child sees <Finalisable object early at 0x109bd8128>
[15336] finalising <Finalisable object early at 0x109bd8128>
### done
用fork
方法,原件定型,child收到这个定型object:
### test drop_early in 15329 method: fork
drop
start
[15329] finalising <Finalisable object early at 0x108b453c8>
[15331] child sees <Finalisable object early at 0x108b453c8>
### done
这表明主进程的有效载荷在 child 进程运行并完成之前完成! 归根结底,args
不是防早collection!
早 collection 共享 objects
Python 有 some types meant for safe sharing between processes。我们也可以使用它作为我们的标记:
def drop_early_shared():
payload = Finalisable(multiprocessing.Value('i', 65))
child = multiprocessing.Process(target=print_pid, args=('child sees', payload,))
print('drop')
del payload
print('start')
child.start()
child.join()
使用 fork
方法,Value
被提早收集但仍然有效:
### test drop_early_shared in 15516 method: fork
drop
start
[15516] finalising <Finalisable object <Synchronized wrapper for c_int(65)> at 0x1071a3e10>
[15519] child sees <Finalisable object <Synchronized wrapper for c_int(65)> at 0x1071a3e10>
### done
使用 spawn
方法,Value
被提早收集并且完全破坏了 child:
### test drop_early_shared in 15520 method: spawn
drop
start
[15520] finalising <Finalisable object <Synchronized wrapper for c_int(65)> at 0x103a16c18>
[15524] finalising <Finalisable object unknown at 0x101aa0128>
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/spawn.py", line 105, in spawn_main
exitcode = _main(fd)
File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/spawn.py", line 115, in _main
self = reduction.pickle.load(from_parent)
File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/synchronize.py", line 111, in __setstate__
self._semlock = _multiprocessing.SemLock._rebuild(*state)
FileNotFoundError: [Errno 2] No such file or directory
### done
这表明最终化行为取决于您的 object 和您的环境。 底线,不要假设你的 object 是 well-behaved!
虽然通过 args
传递数据是一种很好的做法,但这 不会 使主进程免于处理它! Objects 当主进程删除引用时,可能会对提前完成做出糟糕的响应。
由于 CPython 使用 fast-acting 引用计数,您几乎可以立即看到不良影响。然而,其他实现,例如PyPy,可以在任意时间隐藏这样的 side-effects。