【问题标题】:How can I make sure only 4 instances of a function are running (using multiprocessing)?如何确保只有 4 个函数实例正在运行(使用多处理)?
【发布时间】:2018-10-06 21:54:06
【问题描述】:

我正在使用 python 3.6.6 和 requestsbs4 包来下载和解析一些内容,现在我正在下载一些更大的文件 >1gb 并且只使用一个连接它相当慢所以我想加快它同时进行多个下载。

重要的代码片段:

def download(dir, link, name):
    r = requests.get(url, stream=True)
    with open(f'{path}/{filename}', 'wb') as f:
        for chunk in r.iter_content(chunk_size=1024):
            if chunk:
                f.write(chunk)

files = [{'link':'http://...','filename':'somename.7z'}]
download_dir= '~/Downloads'

for file in files:
    #do some things to check if file['link'] is valid and that the file dosen't already exist
    download(download_dir, file['link'], file['filename'])

我想做的是并行运行循环中的内容,确切地说是让循环中的内容同时运行 4 次。

我第一次尝试这样做是像这样使用multiprocessing.Pool.map

def download(dir, link, name):
    r = requests.get(url, stream=True)
    with open(f'{path}/{filename}', 'wb') as f:
        for chunk in r.iter_content(chunk_size=1024):
            if chunk:
                f.write(chunk)

files = [{'link':'http://...','filename':'somename.7z'}]
download_dir= '~/Downloads'

datas = [{'file':f, 'dir':download_dir} for f in files]

worker(data)
    file = data['file']
    download_dir = data['dir']
    #do some things to check if file['link'] is valid and that the file dosen't already exist
    download(download_dir, file['link'], file['filename'])

pool = multiprocessing.Pool(4)
pool.map(worker, datas)

不幸的是,这不起作用,同时开始了超过 4 个下载,我假设它使用了 4 个线程,但是每次一个线程达到网络限制并且没有一个旧的更进一步,它只是启动了另一个 worker 实例.

为了强迫我的程序做我想做的事,我尝试了这种 hacky 方式:

def download(dir, link, name):
    r = requests.get(url, stream=True)
    with open(f'{path}/{filename}', 'wb') as f:
        for chunk in r.iter_content(chunk_size=1024):
            if chunk:
                f.write(chunk)

files = [{'link':'http://...','filename':'somename.7z'}]
download_dir= '~/Downloads'

worker(file, download_dir)
    #do some things to check if file['link'] is valid and that the file dosen't already exist
    download(download_dir, file['link'], file['filename'])

index = 0
while index < len(files):
    pool = multiprocessing.Pool(4)

    for _ in range(4):
        if index < len(files): #check exists cause I'm incrementing index in the inner for loop
            pool.apply_async(worker, (files[index], download_dir,))
        index += 1

    pool.close()
    pool.join()

但是pool.close() 并没有等待所有提交的任务完成,而是中止了下载,并且显然也不允许提交到池的任务在搁置后恢复。

这样做的正确方法是什么?

【问题讨论】:

  • 你能解释一下“线程达到网络限制”是什么意思吗?是否引发了异常,或者进程只是阻塞等待 IO 完成?有错误信息吗?您是如何认识到这一点的?网络限制是多少(连接数、防火墙、带宽等)?
  • 我很抱歉不清楚这一点,我认为它会阻塞 IO,因为代码运行在谷歌云中的 vm 上,下载速度至少为 500Mbit/s 但是当我研究如何很多数据是单线程传输的,它大约是 10Mbit/s。所以我假设正在发生的是for chunk in r.iter_content(chunk_size=1024) 必须等待获得 1024 个字节,并且在那个时候线程池开始另一个并行执行。

标签: python python-multiprocessing


【解决方案1】:

您的代码存在许多逻辑和样式错误。最大的一个是您为输入列表的每个元素创建一个新池!应该只有一个池。

if (index &lt; len(urllist)) 始终为真,因为您在 while index &lt; len(urlist) 内。

pool = multiprocessing.Pool(processes=size) 风格很差;您应该尽可能使用with(又名“上下文管理器”)。这将消除对pool.close()pool.join() 调用的需要。像这样:

with multiprocessing.Pool(processes=size) as pool:
    pool.apply_async(...)

数字 for 循环在 Python 中被认为是糟糕的风格,因为它们很容易避免。如果您只使用 for url in urllist: 或类似的,则不需要像 index = index + 1 这样的样板。

最后,您根本不需要循环,因为存在pool.map()(和pool.map_async())并且可以在一个函数调用中完成所有操作。

【讨论】:

  • 感谢您花时间回答问题,但这并不能解决我使用 pool.map() 的问题,只会导致 python 盯着太多的下载,而不是等待我的 download 函数完成之前从另一个开始。
  • @usbpc102:一旦您解决了我在这里告诉您的错误,请在您的问题底部发布您更新的代码。一旦我们看到您更新的新代码,我们可能会对其进行更多改进,但我认为一旦您应用了此答案中的建议,您的代码将非常接近您的需求。
  • 我更新了我的帖子,更详细地解释了为什么我会按照自己的方式做事。
猜你喜欢
  • 2017-10-01
  • 1970-01-01
  • 2010-09-27
  • 2012-05-17
  • 1970-01-01
  • 2020-09-21
相关资源
最近更新 更多