多进程池意外终止线程

3

我有一个简单的测试程序,使用 Python 3.6.3 编写并执行。它在一台拥有四个核心的机器上运行。

import multiprocessing
import time

def f(num):
  print(multiprocessing.current_process(), num)
  time.sleep(1)
  if (num % 2):
    raise Exception


pool = multiprocessing.Pool(5)

try:
  pool.map(f, range(1,20))
except Exception as e:
  print("EXCEPTION")

pool.close()
pool.join()

使用 pool = multiprocessing.Pool(5) 输出:

<ForkProcess(ForkPoolWorker-1, started daemon)> 1
<ForkProcess(ForkPoolWorker-2, started daemon)> 2
<ForkProcess(ForkPoolWorker-3, started daemon)> 3
<ForkProcess(ForkPoolWorker-4, started daemon)> 4
<ForkProcess(ForkPoolWorker-5, started daemon)> 5
<ForkProcess(ForkPoolWorker-2, started daemon)> 6
<ForkProcess(ForkPoolWorker-1, started daemon)> 7
<ForkProcess(ForkPoolWorker-4, started daemon)> 8
<ForkProcess(ForkPoolWorker-3, started daemon)> 9
<ForkProcess(ForkPoolWorker-5, started daemon)> 10
<ForkProcess(ForkPoolWorker-2, started daemon)> 11
<ForkProcess(ForkPoolWorker-1, started daemon)> 12
<ForkProcess(ForkPoolWorker-4, started daemon)> 13
<ForkProcess(ForkPoolWorker-3, started daemon)> 14
<ForkProcess(ForkPoolWorker-5, started daemon)> 15
<ForkProcess(ForkPoolWorker-2, started daemon)> 16
<ForkProcess(ForkPoolWorker-1, started daemon)> 17
<ForkProcess(ForkPoolWorker-3, started daemon)> 18
<ForkProcess(ForkPoolWorker-4, started daemon)> 19
EXCEPTION

但是,如果我将进程池的处理器数量更改为等于或小于我的计算机上的核心数,那么每次调用f()(其中num为偶数)时,都不会打印输出。

使用pool = multiprocessing.Pool(4)的输出结果:

<ForkProcess(ForkPoolWorker-1, started daemon)> 1
<ForkProcess(ForkPoolWorker-2, started daemon)> 3
<ForkProcess(ForkPoolWorker-3, started daemon)> 5
<ForkProcess(ForkPoolWorker-2, started daemon)> 7
<ForkProcess(ForkPoolWorker-1, started daemon)> 9
<ForkProcess(ForkPoolWorker-3, started daemon)> 11
<ForkProcess(ForkPoolWorker-3, started daemon)> 13
<ForkProcess(ForkPoolWorker-1, started daemon)> 15
<ForkProcess(ForkPoolWorker-2, started daemon)> 17
<ForkProcess(ForkPoolWorker-1, started daemon)> 19
EXCEPTION

我不明白为什么这些进程会被终止,特别是当异常直到函数中的打印语句之后才被抛出。更让我困惑的是,只有在池中的进程计数等于或小于机器上的核心数时,才会发生这种情况。


2
如果你正在使用Windows操作系统,请通过添加以下代码保护你的程序:if __name__ == '__main__': - Jean-François Fabre
3
注意:它们不是线程,而是进程。 - Jean-François Fabre
@Jean-FrançoisFabre 但是为什么竞争条件只会在 =< cpu_count 发生呢? - Zack Thomas
1
请参考以下链接:https://dev59.com/WIHba4cB1Zd3GeqPVsQN#24894997 - Jean-François Fabre
我不认为这是一个竞态条件,@Jean-FrançoisFabre。这与默认块大小为“天真”数字的1/4有关,请参见https://github.com/python/cpython/blob/bab4bbb4c9cd5d25ede21a1b8c99d56e3b8dae9d/Lib/multiprocessing/pool.py#L413(如下面的答案中很好地解释了)。 - Brad Solomon
显示剩余3条评论
1个回答

3

关于multiprocessing.Pool.map的规范,你可以看到有一个可选参数chunksize,如果你将其指定为1,即 pool.map(f, range(1,20), 1),那么你将得到预期的结果。

如果你增加块大小(例如=6),你可能会看到:

<SpawnProcess(SpawnPoolWorker-1, started daemon)> 1
<SpawnProcess(SpawnPoolWorker-4, started daemon)> 7
<SpawnProcess(SpawnPoolWorker-3, started daemon)> 13
<SpawnProcess(SpawnPoolWorker-2, started daemon)> 19

这意味着在池中,将一定数量的chunksize个任务分配给单个线程,当您在每个线程期间引发异常时,剩余块中的任务将不会被执行。

从这里可以知道,在您的情况下,默认值chunksize为2,存在这样的变量的原因很容易看到,是为了减少需要初始化的新线程数量(当您有适当的块大小时,这可能节省资源和处理时间)。


不错的回答。稍微解释一下 - 调用.map()会使用.submit()生成任务,但它不会加入它们(除非您将pool作为上下文管理器使用 - 然后加入将在__exit__上发生)。因此,异常确实会发生,但不会“显示出来”,因为.map()返回一个Future对象迭代器。 - Brad Solomon
2
此外,正如您所指出的,在这种特定情况下,chunksize 为2,其计算方法可以在此处找到,并且还有一些解释可以在此处找到。 - Brad Solomon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接