在本教程中,我们将学习如何使用 Python 实现多线程和多处理方法。这些方法指导操作系统优化使用系统硬件,从而提高代码执行效率。多线程引用 Wiki 的解释—在计算机体系结构中,多线程是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,进而提升整体处理性能。并发指的是可以实现多个进程的并行执行,从而实现更快的运行时间。当执行基于 I/O 的任务(如下载图像和文件)时,多线程是更有效的,另一方面多处理也适合于基于 CPU 的计算密集型任务。Python 中的多线程实现为了实现多线程,我们将使用 Python 的标准库 threading。默认情况下,该库 Python 会默认安装,因此可以直接在代码中导入。为了演示多线程的有效性,我们将从 Unsplash 下载 5 幅图像。让我们观察一下当我们按顺序下载这些图像时的执行时间:#### 导入请求库 import requests

 

#### 定义函数 def down_img(name,link):    data = requests.get(link).content    name = f"/home/isud/DidYouKnow/Tutorial 5/{name}.jpg"    with open(name, "wb") as file:        file.write(data)

 

#### 连续下载 5 张图片%%timeit -n1 -r1images = ['https://images.unsplash.com/photo-1531458999205-f31f14fa217b',          'https://images.unsplash.com/photo-1488572749058-7f52dd70e0fa',          'https://images.unsplash.com/photo-1531404610614-68f9e73e35db',          'https://images.unsplash.com/photo-1523489405193-3884f5ca475f',          'https://images.unsplash.com/photo-1565098735462-5db3412ac4cb']for i,link in enumerate(images):    down_img(i,link)

 

#### %%timeit results51.4 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)可以观察到,5 张图片的完整下载耗时 51.4 秒,而且只有在上一次下载结束后才开始新的下载。现在让我们看看多线程如何提高代码性能。#### 导入必要的库 import threadingimport requests

 

#### 定义函数 def down_img(name,link):    data = requests.get(link).content    name = f"/home/isud/DidYouKnow/Tutorial 5/{name}.jpg"    with open(name, "wb") as file:        file.write(data)

 

#### 并行线程下载图像%%timeit -n1 -r1threads = []images = ['https://images.unsplash.com/photo-1531458999205-f31f14fa217b',          'https://images.unsplash.com/photo-1488572749058-7f52dd70e0fa',          'https://images.unsplash.com/photo-1531404610614-68f9e73e35db',          'https://images.unsplash.com/photo-1523489405193-3884f5ca475f',          'https://images.unsplash.com/photo-1565098735462-5db3412ac4cb']for i,link in enumerate(images):    t = threading.Thread(target=down_img, args=(i,link))    t.start()    threads.append(t)

for thread in threads:    thread.join()

 

#### %%timeit results25.6 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)代码解释-定义图像下载循环:第 1 步(线程初始化)——Python 在一个线程中运行完整的代码(我们称之为主线程)。在本例中,通过从线程库调用 Thread 函数,我们启动并行线程并为它们分配一个要执行的目标进程(在本例中为 down_image)。被调用函数所需的所有参数都应作为序列对象(在本例中为元组)传递。对 Thread 函数的每次调用都会启动一个新线程(我们称之为并行线程)。第 2 步(线程启动)——调用线程的 start 方法将指示 Python 启动线程执行。如果 for 循环在主线程中执行,而函数调用在并行线程中,则 for 循环的执行将在图片下载过程中继续执行。步骤 3(线程的 join)——每个新线程都被捕获到一个名为 threads 的列表中,然后通过调用 join 方法将并行线程连接到主线程。为什么需要使用 join?在第 2 步之前,我们所有的线程(主线程和并行线程)都是并行执行的,在这种情况下,主线程完成任务的时间可以比并行线程完成任务早很多,及主线程会结束更早。为了避免这种情况,将并行线程连接到主线程是必须的,这将确保只有在并行线程完成之后才完成主线程的执行。下图说明了这两种情况:

 

 

可以看出,下载图像的执行时间减少了近 50%(大约 25.6 秒)。上面的示例展示了多线程在 I/O 操作中的帮助,以及如何提高下载/上传过程的效率。多处理与在单个进程中执行多个线程的多线程不同,多处理为每个任务启动一个新的并行进程。如前所述,它为 CPU 密集型任务(需要大量计算的任务)提供了相当大的运行时改进。在 Python 中实现多处理 multiprocessing 是另一个在 Python 中支持多处理特性的标准库,为了理解它的功能,我们将多次调用一个计算密集型函数,来计算从 1 到 1 千万的数字的平方。此函数并行执行 8 次,让我们观察一下这个函数在正常情况下的性能。#### 导入时间库 import time

 

#### 定义函数 def demo_func(num):    for i in range(num):        a = i**2

#### 顺序调用演示函数%%timeit -n1 -r1for i in range(8):    demo_func(10000000)

#### %%timeit 结果 21.2 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)演示函数的顺序执行总共花费了 21.2 秒,现在让我们检查在多处理设置中执行此操作时的性能提升。#### 导入库 import time

#### 定义函数 def demo_func(num):    for i in range(num):        a = i**2

#### 多处理 demo 函数%%timeit -n1 -r1processes = []lop_size = [10000000,10000000,10000000,10000000,10000000,10000000,10000000, 10000000]p = multiprocessing.Pool()p.map(demo_func,lop_size)p.close()p.join()

#### %%timeit 结果 11.6 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)在多处理框架下,执行时间减少了 50%,达到 11.6 秒。在顺序处理中,一次使用一个 CPU 内核,而在多个处理中,所有系统内核都是并行使用的。CPU 使用率屏幕截图显示了相同的情况:

 

 

上图中的每一行代表一个 CPU 核。请注意,在顺序执行中,每个函数调用都会触发一个核,而在并行执行中,所有核都是同时触发的。代码说明步骤 1(池创建)-池方法创建可并行利用的进程池。在没有任何参数的情况下,创建的进程数等于系统上的 CPU 核数。我有一个四核系统,这意味着,在执行时的 8 个函数调用中,前 4 个调用将并行运行,然后是下 4 个函数调用。请注意,你还可以在池中定义一个自定义的进程数(多于内核数),但超过某个值时,它将开始占用系统内存并可能降低性能步骤 2(池映射)-这是指示进程执行特定函数(第一个参数)以及要传递给它的参数列表(第二个参数)步骤 3(Pool Close)-Close 方法指示 Python 解释器,我们已经提交了要提交给池的所有内容,将来不再向池提供更多的输入。步骤 4(池连接)-与线程的情况一样,Join 方法确保代码执行只在所有并行进程完成后完成。从上面的场景中,我们可以看到多处理是如何在高效的代码性能方面成为一个很好的帮手。结束本教程中,我们将重点放在通过优化系统硬件来提高代码性能上。希望这篇教程能帮助你,你能学到一些新东西。