为啥“multiprocessing.Pool”在 Windows 上无休止地运行?

Posted

技术标签:

【中文标题】为啥“multiprocessing.Pool”在 Windows 上无休止地运行?【英文标题】:Why does "multiprocessing.Pool" run endlessly on Windows?为什么“multiprocessing.Pool”在 Windows 上无休止地运行? 【发布时间】:2021-06-01 08:53:20 【问题描述】:

我已经定义了函数get_content 来从https://www.investopedia.com/ 抓取数据。我试过get_content('https://www.investopedia.com/terms/1/0x-protocol.asp'),它奏效了。但是,该过程似乎在我的 Windows 笔记本电脑上无限运行。我检查了它在 Google Colab 和 Linux 笔记本电脑上运行良好。

您能否详细说明为什么我的函数在这种并行设置中不起作用?

import requests
from bs4 import BeautifulSoup
from multiprocessing import dummy, freeze_support, Pool
import os
core = os.cpu_count() # Number of logical processors for parallel computing
headers = 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0'
session = requests.Session() 
links = ['https://www.investopedia.com/terms/1/0x-protocol.asp', 'https://www.investopedia.com/terms/1/1-10net30.asp']

############ Get content of a word
def get_content(l):
    r = session.get(l, headers = headers)
    soup = BeautifulSoup(r.content, 'html.parser')
    entry_name = soup.select_one('#article-heading_3-0').contents[0]
    print(entry_name)

############ Parallel computing 
if __name__== "__main__":
    freeze_support()
    P_d = dummy.Pool(processes = core)
    P = Pool(processes = core)   
    #content_list = P_d.map(get_content, links)
    content_list = P.map(get_content, links)

Update1:​​我在 JupyterLab 中从 Anaconda 发行版中运行此代码。从下面的截图可以看出,状态一直是busy

Update2:代码的执行可以在Spyder中完成,但它仍然没有返回任何输出。

Update3:代码在 Colab 中运行良好:

【问题讨论】:

在将所有共享数据移动到if __name__ == "__main__":(我在 Windows 上运行)后对我来说效果很好。 顺便说一句,MP 在这方面可能有点矫枉过正。由于您只是触发请求并且大部分工作都是 IO 绑定的,因此多线程似乎更适合,因为线程可以只是照看请求。 MP 更适合 CPU 密集型工作。见this answer @ggorlen 你能详细说明“移动所有共享数据......”是什么意思吗? 请参阅this answer--我认为如果您使用的是 linux/mac 并且它应该开箱即用,这不是问题。不过,我确实将 session 对象传递给了每个工作函数,但我不确定这是否重要。 @ggorlen 我尝试将并行代码放入if __name__ == "__main__":,但没有成功。 Here 是我的修改。是的,我在 Windows 10 上运行。您能否发布您的代码作为答案? 【参考方案1】:

这里有很多解压,但基本上都归结为python如何启动一个新进程,并执行你想要的函数。

在 *nix 系统上,创建新进程的默认方法是使用 fork。这很棒,因为它使用“写时复制”来让新的子进程访问父工作内存的副本。它快速高效,但如果同时使用多线程,它会带来一个明显的缺点。并非所有内容实际上都被复制了,有些东西可以在无效状态下被复制(线程、互斥体、文件句柄等)。如果处理不当,这可能会导致很多问题,要绕过这些 python 可以使用 spawn 代替(Windows 也没有“fork”,必须使用“spawn”)。

Spawn 基本上是从头开始一个新的解释器,并且不会以任何方式复制父级的内存。然而,必须使用某种机制来让孩子访问在创建之前定义的函数和数据,而 python 通过从创建它的“.py”文件中基本上拥有import * 的新进程来做到这一点。 这是交互模式的问题,因为import 并没有真正的“.py”文件,并且是“多处理不喜欢交互”问题的主要来源。 将您的 mp 代码放入库中,然后导入并执行 确实 以交互方式工作,因为它可以从“.py”文件中导入。这也是为什么我们使用if __name__ == "__main__": 行来分隔任何您不想在导入发生时在子级中重新执行的代码。如果您要在没有这个的情况下生成一个新进程,它可能会递归地继续生成子进程(尽管从技术上讲,针对特定情况 iirc 有一个内置的保护)。

然后使用任一启动方法,父级通过pipe 与子级通信(使用pickle 交换python 对象)告诉它调用什么函数,以及参数是什么。这就是为什么论点必须是可腌制的。 有些东西不能腌制,这是multiprocessing中另一个常见的错误来源。

最后一点,IPython 解释器(默认的 Spyder shell)在使用“spawn”时并不总是从子进程中收集标准输出或标准错误,这意味着不会显示print 语句。 vanilla (python.exe) 解释器可以更好地处理这个问题。

在您的具体情况下:

Jupyter 实验室正在交互模式下运行,子进程将已创建,但出现类似“无法从 __main__ 导入 get_content”的错误。错误没有正确显示,因为它没有发生在主进程中,并且 jupyter 没有正确处理来自子进程的 stderr Spyder 正在使用 IPython,默认情况下它不会将 print 语句从子代传递给父代。在这里,您可以在“运行”对话框中切换到“外部系统控制台”,但您还必须采取措施使窗口保持足够长的时间以读取输出(防止进程退出)。 Google Colab 正在使用运行 Linux 的 google 服务器来执行您的代码,而不是在您的 Windows 机器上本地执行它,因此通过使用“fork”作为启动方法,没有“.py”文件的特殊问题import from 不是问题。

【讨论】:

以上是关于为啥“multiprocessing.Pool”在 Windows 上无休止地运行?的主要内容,如果未能解决你的问题,请参考以下文章

Windows 上的 Multiprocessing.pool.Pool:CPU 限制为 63?

multiprocessing.Pool() 比只使用普通函数慢

是否可以将多个参数传递给 multiprocessing.pool? [复制]

如何在 Python 中使用 multiprocessing.pool 创建全局锁/信号量?

如何将二维数组作为 multiprocessing.Array 传递给 multiprocessing.Pool?

Python 多进程编程之multiprocessing--Pool