Python 多进程 进程池的使用

使用进程池处理多任务

Pool类

在使用Python进行系统管理时,特别是同时操作多个文件目录或者远程控制多台主机,并行操作可以节约大量的时间。如果操作的对象数目不大时,还可以直接使用Process类动态的生成多个进程,十几个还好,但是如果上百个甚至更多,那手动去限制进程数量就显得特别的繁琐,此时进程池就派上用场了。 
Pool类可以提供指定数量的进程供用户调用,当有新的请求提交到Pool中时,如果池还没有满,就会创建一个新的进程来执行请求。如果池满,请求就会告知先等待,直到池中有进程结束,才会创建新的进程来执行这些请求。

Pool的默认大小是CPU的核数


下面介绍一下multiprocessing 模块下的Pool类下的几个方法

apply()

函数原型:

apply(func[, args=()[, kwds={}]])

该函数用于传递不定参数,主进程会被阻塞直到函数执行结束(不建议使用,并且3.x以后不在出现)。

apply_async()

函数原型:

apply_async(func[, args=()[, kwds={}[, callback=None]]])

与apply用法一样,但它是非阻塞且支持结果返回进行回调。

map()

函数原型:

map(func, iterable[, chunksize=None])

Pool类中的map方法,与内置的map函数用法行为基本一致,它会使进程阻塞直到返回结果。 
注意,虽然第二个参数是一个迭代器,但在实际使用中,必须在整个队列都就绪后,程序才会运行子进程。

close()

关闭进程池(pool),使其不在接受新的任务。

terminate()

结束工作进程,不在处理未处理的任务。

扫描二维码关注公众号,回复: 92973 查看本文章

join()

主进程阻塞等待子进程的退出,join方法必须在close或terminate之后使用。

对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),让其不再接受新的Process了。

先创建容量为3的进程池,然后将f(i)依次传递给它,运行脚本后利用ps aux | grep pool.py查看进程情况,会发现最多只会有三个进程执行。pool.apply_async()用来向进程池提交目标请求,pool.join()是用来等待进程池中的worker进程执行完毕,防止主进程在worker进程结束前结束。但必pool.join()必须使用在pool.close()或者pool.terminate()之后。其中close()跟terminate()的区别在于close()会等待池中的worker进程执行结束再关闭pool,而terminate()则是直接关闭。result.successful()表示整个调用执行的状态,如果还有worker没有执行完,则会抛出AssertionError异常。

from multiprocessing import Pool
import os, time, random


def long_time_task(name):
    # 待执行的任务,每个任务默认执行1s,便于统计
    print(f'Run task {name}, pid {os.getpid()}')
    t = 1
    time.sleep(t)
    print(f'Task {name} finished, spend {t} seconds.')


if __name__ == '__main__':
    print('Parent process %s.' % os.getpid())

    # 初始化进程池,设置最大进程数,默认是4
    # 此处设置10个,结果应该是执行总时间的十分之一
    p = Pool(10)
    start = time.time()
    for i in range(100):
        p.apply_async(long_time_task, args=(i,))
        # p.apply(long_time_task, args=(i,))  # 阻塞调用,非异步
    print('Waiting for all subprocesses done...')
    # 关闭进程池,不再接受新的任务
    p.close()
    # 阻塞主进程,知道进程池中的进程完成任务
    p.join()
    end = time.time()
    print('All subprocesses done.', end - start)

结果与预期一致,花费时间是单进程的十分之一

All subprocesses done. 10.46172046661377

开50个进程的结果

All subprocesses done. 2.997833251953125

开100个进程的结果

All subprocesses done. 2.8084609508514404

开一看到并非进程数目越多越好(有可能是我电脑太垃圾。。。)

使用map和map_async可以一次加入多个任务 ,但是只支持1个参数的函数定义,多个参数需要改为元组或者对象

p.map_async(long_time_task, range(10))

# 等价于
for i in range(10):
       p.apply_async(long_time_task, args=(i,))

使用多进程爬取豆瓣影评

​
import requests
from multiprocessing import Pool
import json

MAX_PRO = 50
root = 'd:/data/douban/hot_review'
def saveHtml(reviewId):
    url = f'https://movie.douban.com/review/{reviewId}/'
    html = requests.get(url).text
    path = f'{root}/{reviewId}.html'
    # print(url, path, html)
    with open(path, encoding='utf8', mode='w+') as f:
        f.write(html)
    print(url)


def saveAll(reviewIds):
    pool = Pool(MAX_PRO)
    for i in reviewIds:
        pool.apply_async(saveHtml, (i,))
    pool.close()
    print('wait...')
    pool.join()
    print('finish')


if __name__ == "__main__":
    with open('../hot_review.json', encoding='utf8', mode='r') as f:
        reviews = json.load(f)
    reviewIds = [i['id'] for i in reviews]
    saveAll(reviewIds)

​

猜你喜欢

转载自my.oschina.net/ahaoboy/blog/1791444