测试开发之Python核心笔记(25): 多进程与多线程

25.1 并发和并行

  • 并发Concurrency

    • 工作互相切换,如线程切换和任务切换
    • 多线程(线程切换)、协程(任务切换)
  • 并行Parallelism

    • 同一时刻、同时发生
    • 多进程
  • 并行还是并发?

    • 并发通常应用于 I/O 操作频繁的场景,比如你要从网站上下载多个文件,I/O 操作的时间可能会比 CPU 运行处理的时间长得多。
    • 而并行则更多应用于 CPU heavy 的场景,比如 MapReduce 中的并行计算,为了加快运行速度,一般会用多台机器、多个处理器来完成。

25.2 多线程

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(),用来从完成后的迭代器中,取出对应的结果或异常。

25.3 多进程

Python3提供了concurrent.futures.ProcessPoolExecutor()进程池类。

多进程可以以并行的方式去提高程序运行效率。针对上面的代码,只需要在 download_all() 函数中,做出下面的变化即可:

with futures.ThreadPoolExecutor(workers) as executor
=>
with futures.ProcessPoolExecutor() as executor: 

在需要修改的这部分代码中,函数 ProcessPoolExecutor() 表示创建进程池,使用多个进程并行的执行程序。不过,这里我们通常省略参数 workers,因为系统会自动返回 CPU 的数量作为可以调用的进程数。其他部分跟多线程一样。

你可能感兴趣的:(Python)