[英]Python Multiprocessing weird behavior when NOT USING time.sleep()
这是来自 Python.org 的确切代码。 如果您注释掉time.sleep()
,它会崩溃并带有很长的异常回溯。 我想知道为什么。
而且,我确实理解为什么 Python.org 将其包含在他们的示例代码中。 但是通过time.sleep()
人为地创建“工作时间”不应该在删除代码时破坏代码。 在我看来, time.sleep()
提供了某种启动时间。 但正如我所说,我想从可能真正知道答案的人那里知道。
用户评论要求我填写有关发生这种情况的环境的更多详细信息。它在 OSX Big Sur 11.4 上。 使用来自 Python.org 的 Python 3.95 的全新安装(没有 Homebrew 等)。 从 venv 内的 Pycharm 内运行。 我希望这有助于加深对情况的了解。
import time
import random
from multiprocessing import Process, Queue, current_process, freeze_support
#
# Function run by worker processes
#
def worker(input, output):
for func, args in iter(input.get, 'STOP'):
result = calculate(func, args)
output.put(result)
#
# Function used to calculate result
#
def calculate(func, args):
result = func(*args)
return '%s says that %s%s = %s' % \
(current_process().name, func.__name__, args, result)
#
# Functions referenced by tasks
#
def mul(a, b):
#time.sleep(0.5*random.random()) # <--- time.sleep() commented out
return a * b
def plus(a, b):
#time.sleep(0.5*random.random()). # <--- time.sleep() commented out
return a + b
#
#
#
def test():
NUMBER_OF_PROCESSES = 4
TASKS1 = [(mul, (i, 7)) for i in range(20)]
TASKS2 = [(plus, (i, 8)) for i in range(10)]
# Create queues
task_queue = Queue()
done_queue = Queue()
# Submit tasks
for task in TASKS1:
task_queue.put(task)
# Start worker processes
for i in range(NUMBER_OF_PROCESSES):
Process(target=worker, args=(task_queue, done_queue)).start()
# Get and print results
print('Unordered results:')
for i in range(len(TASKS1)):
print('\t', done_queue.get())
# Add more tasks using `put()`
for task in TASKS2:
task_queue.put(task)
# Get and print some more results
for i in range(len(TASKS2)):
print('\t', done_queue.get())
# Tell child processes to stop
for i in range(NUMBER_OF_PROCESSES):
task_queue.put('STOP')
if __name__ == '__main__':
freeze_support()
test()
如果它对任何人有帮助,这是回溯:
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/spawn.py", line 116, in spawn_main
exitcode = _main(fd, parent_sentinel)
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/spawn.py", line 126, in _main
self = reduction.pickle.load(from_parent)
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/synchronize.py", line 110, in __setstate__
Traceback (most recent call last):
File "<string>", line 1, in <module>
self._semlock = _multiprocessing.SemLock._rebuild(*state)
FileNotFoundError: [Errno 2] No such file or directory
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/spawn.py", line 116, in spawn_main
exitcode = _main(fd, parent_sentinel)
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/spawn.py", line 126, in _main
self = reduction.pickle.load(from_parent)
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/synchronize.py", line 110, in __setstate__
self._semlock = _multiprocessing.SemLock._rebuild(*state)
FileNotFoundError: [Errno 2] No such file or directory
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/spawn.py", line 116, in spawn_main
exitcode = _main(fd, parent_sentinel)
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/spawn.py", line 126, in _main
self = reduction.pickle.load(from_parent)
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/synchronize.py", line 110, in __setstate__
self._semlock = _multiprocessing.SemLock._rebuild(*state)
FileNotFoundError: [Errno 2] No such file or directory
这是一个技术故障。
这是一个竞争条件,主进程完成,并在一些子进程有机会完全启动之前退出。 只要孩子完全开始,就会有适当的机制确保他们顺利关闭,但中间的时间是不安全的。 竞争条件可能非常依赖于系统,因为它取决于操作系统和硬件来安排不同的线程,以及它们处理工作的速度。
这是一个进程启动时发生的事情......在创建子进程的早期,它会在主进程中注册自己,以便在主进程退出时它将被join
或terminate
,这取决于它是否是守护进程( multiprocessing.util._exit_function
)。 此出口 function 在多处理导入时向atexit
模块注册。
同样在创建子进程期间,会打开一对Pipe
,它们将用于将Process
object 传递给子解释器(其中包括您想要执行的 function 及其参数)。 这需要与孩子共享 2 个文件句柄,并且这些文件句柄也注册为使用atexit
关闭。
当主进程在子进程有机会在启动阶段从 pipe 读取所有必要数据(取消Process
对象)之前退出时,就会出现问题。 如果主进程先关闭pipe,然后等待子进程join
,那么我们就有问题了。 孩子将继续旋转新的 python 实例,直到它需要读取包含您的 function 和 ZFDBC11CAA58B666Z 的Process
object 应该运行。 它将尝试从已经关闭的 pipe 读取,这是一个错误。
如果所有孩子都有机会完全启动,您将永远不会看到这一点,因为 pipe 仅用于启动。 延迟以某种方式保证所有孩子都有时间完全启动是解决这个问题的方法。 手动调用join
将通过在调用任何atexit
处理程序之前等待子进程来提供此延迟。 此外,任何数量的处理延迟都意味着主线程中的q.get
必须等待一段时间,这也给了子线程在关闭之前启动的时间。 我永远无法重现您遇到的问题,但大概您从所有TASKS
中看到了 output (“Process-1 说 mul(19, 7) = 133” )。 只有一两个子进程完成了所有工作,让主进程get
所有结果,并在其他子进程完成启动之前完成。
编辑:
该错误对于正在发生的事情是明确的,但我仍然无法弄清楚它是如何发生的......据我所知,在加入或终止所有active_children
而不是在_exit_function
中调用_run_finalizers()
时应该关闭文件句柄之前通过_run_finalizers(0)
编辑2:
_run_finalizers
似乎实际上永远不会调用Popen.finalizer
来关闭管道,因为exitpriority
是None
。 我对这里发生的事情感到非常困惑,我想我需要睡在上面......
显然@user2357112supportsMonica 是在正确的轨道上。 如果您在退出程序之前加入进程,它可以完全解决问题。 @Aaron 的回答也深入了解了为什么这可以解决问题!
我按照建议添加了以下代码,它完全解决了在其中设置time.sleep()
的需要。
首先,我收集了启动时的所有进程:
processes: list[Process] = []
# Start worker processes
for i in range(NUMBER_OF_PROCESSES):
p = Process(target=worker, args=(task_queue, done_queue))
p.start()
processes.append(p)
然后在程序结束时,我加入了他们,如下所示:
# Join the processes
for p in processes:
p.join()
彻底解决了问题。 感谢您的建议。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.