并发Concurrency
并行Parallelism
并行还是并发?
Python标准库为我们提供了threading和multiprocessing模块编写相应的多线程/多进程代码。从Python3中通过concurrent.futures 模块支持多线程编程。其中有一个concurrent.futures.ThreadPoolExecutor()类,提供线程池,提高了多线程处理的效率。需要根据实际的需求做一些测试,来寻找最优的线程数量。
通过代码来学习:
import concurrent.futures # 引入futures模块
import time
import requests
# 定义一个原子任务
def download(url):
resp = requests.get(url, headers={'User-Agent': "PostmanRuntime/7.24.0"})
print('Download {} from site {}'.format(len(resp.text), url))
return len(resp.text)
# 在多线程中执行原子任务
def download_all(urls):
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor: # 声明一个5个线程的线程池
# worker.map(download, urls) # 不需要download函数的结果时可以这样。
to_do = [] # future列表
results = [] # future的结果列表
for url in urls:
# submit返回创建好的 future 实例,以便之后查询结果使用
future = executor.submit(download, url)
to_do.append(future)
print(to_do)
# as_completed针对给定的 future 迭代器 to_do,返回其完成后的迭代器done_future
for done_future in concurrent.futures.as_completed(to_do):
print(done_future)
# future在done_future里面的顺序,与to_do里面的顺序不一定一致,所以urls与results也不是顺序对应的
results.append(done_future.result()) # result是future执行后的结果或者异常
return results
if __name__ == '__main__':
sites = ['https://movie.douban.com/subject/34805219/',
'https://movie.douban.com/subject/2364086/',
'https://movie.douban.com/subject/33411505',
'https://movie.douban.com/subject/34670959',
'https://movie.douban.com/subject/30403338',
'https://movie.douban.com/subject/30211998',
'https://movie.douban.com/subject/30182726',
'https://movie.douban.com/subject/34670218',
'https://movie.douban.com/subject/26348103',
'https://movie.douban.com/subject/30252495',
'https://movie.douban.com/subject/34805219/',
'https://movie.douban.com/subject/2364086/',
'https://movie.douban.com/subject/33411505',
'https://movie.douban.com/subject/34670959',
'https://movie.douban.com/subject/30403338',
'https://movie.douban.com/subject/30211998',
'https://movie.douban.com/subject/30182726',
'https://movie.douban.com/subject/34670218',
'https://movie.douban.com/subject/26348103',
'https://movie.douban.com/subject/30252495', ]
start_time = time.perf_counter()
res = download_all(sites)
end_time = time.perf_counter()
print("Escape time:", end_time - start_time)
当我们执行 executor.submit(func) 时,它便会安排里面的 func() 函数执行,并返回创建好的 future 实例,以便你之后查询调用。
as_completed(fs),则是针对给定的 future 迭代器 to_do,在其完成后,返回完成后的迭代器。Futures 中还有一个重要的函数 result(),用来从完成后的迭代器中,取出对应的结果或异常。
Python3提供了concurrent.futures.ProcessPoolExecutor()进程池类。
多进程可以以并行的方式去提高程序运行效率。针对上面的代码,只需要在 download_all() 函数中,做出下面的变化即可:
with futures.ThreadPoolExecutor(workers) as executor
=>
with futures.ProcessPoolExecutor() as executor:
在需要修改的这部分代码中,函数 ProcessPoolExecutor() 表示创建进程池,使用多个进程并行的执行程序。不过,这里我们通常省略参数 workers,因为系统会自动返回 CPU 的数量作为可以调用的进程数。其他部分跟多线程一样。