Python中多线程和多处理的区别

Python中多线程和多处理的区别

在本文中,我们将了解 Python 中多线程和多处理的内容、原因和方式。在深入研究代码之前,让我们了解这些术语的含义。

  • 程序是一个可执行文件,它由一组执行某些任务的指令组成,通常存储在您的计算机磁盘上。
  • 进程就是我们所说的程序,它连同它运行所需的所有资源一起被加载到内存中。它有自己的内存空间。
  • 线程是进程中的执行单元。一个进程可以有多个线程作为它的一部分运行,每个线程使用进程的内存空间并与其他线程共享。
  • 多线程是一种技术,其中一个进程产生多个线程以执行不同的任务,大约在同一时间,一个接一个。这会给您一种线程并行运行的错觉,但它们实际上是以并发方式运行的。在 Python 中,全局解释器锁 (GIL) 可防止线程同时运行。
  • 多处理是一种实现最真实形式的并行性的技术。多个进程跨多个 CPU 内核运行,它们之间不共享资源。每个进程可以在自己的内存空间中运行许多线程。在 Python 中,每个进程都有自己的 Python 解释器实例来完成执行指令的工作。

现在,让我们进入程序,我们尝试执行两种不同类型的函数:IO 密集型和 CPU 密集型以六种不同的方式。在 IO-bound 函数中,我们要求 CPU 空闲并打发时间,而在 CPU-bound 函数中,CPU 将忙于生成一些数字。

要求:

  • 一台 Windows 计算机(我的机器有 6 个内核)。
  • 已安装 Python 3.x。
  • 任何用于编写 Python 程序的文本编辑器/IDE(我在这里使用 Sublime Text)。

注意: 下面是我们程序的结构,这将在所有六个部分中通用。在提到 # YOUR CODE# 的地方,将其替换为每个部分的代码片段。

import time, os
from threading import Thread, current_thread
from multiprocessing import Process, current_process


COUNT = 200000000
SLEEP = 10

def io_bound(sec):

    pid = os.getpid()
    threadName = current_thread().name
    processName = current_process().name

    print(f"{pid} * {processName} * {threadName} 
        ---> Start sleeping...")
    time.sleep(sec)
    print(f"{pid} * {processName} * {threadName} 
        ---> Finished sleeping...")

def cpu_bound(n):

    pid = os.getpid()
    threadName = current_thread().name
    processName = current_process().name

    print(f"{pid} * {processName} * {threadName} 
        ---> Start counting...")

    while n>0:
        n -= 1

    print(f"{pid} * {processName} * {threadName} 
        ---> Finished counting...")

if __name__=="__main__":
    start = time.time()

    # YOUR CODE #

    end = time.time()
    print('Time taken in seconds -', end - start)
Python

第1部分: 运行 IO-bound 任务两次,一个接一个……

# Code snippet for Part 1
io_bound(SLEEP)
io_bound(SLEEP)
Java

在这里,我们要求 CPU 执行函数 io_bound() ,该函数接受一个整数(此处为 10)作为参数,并要求 CPU 休眠这么多秒。此执行总共需要 20 秒,因为每个函数执行需要 10 秒才能完成。请注意,同一个 MainProcess 调用函数两次,一次又一次,使用它的默认线程 MainThread

Python中多线程和多处理的区别

第2部分: 使用线程来运行 IO-bound 任务……

# Code snippet for Part 2
t1 = Thread(target = io_bound, args =(SLEEP, ))
t2 = Thread(target = io_bound, args =(SLEEP, ))
t1.start()
t2.start()
t1.join()
t2.join()
Java

在这里,让我们在 Python 中使用线程来加速函数的执行。线程 Thread-1 和 Thread-2 由我们的 MainProcess 启动,每个线程几乎同时调用我们的函数。两个线程同时完成它们睡眠 10 秒的工作。这将我们整个程序的总执行时间显着减少了 50%。因此,多线程是执行任务的首选解决方案,其中我们的 CPU 的空闲时间可用于执行其他任务。因此,通过利用等待时间来节省时间。
Python中多线程和多处理的区别

第3部分: 运行 CPU 密集型任务两次,一个接一个……

# Code snippet for Part 3
cpu_bound(COUNT)
cpu_bound(COUNT)
Java

在这里,我们将调用函数 cpu_bound(),它接受一个大数字(此处为 200000000)作为参数,并在每一步递减它直到它为零。CPU 被要求在每个函数调用时进行倒计时,这大约需要 12 秒(这个数字在不同的机器上可能会有所不同)。因此,整个程序的执行需要我大约 26 秒才能完成。请注意, MainProcess 再次调用该函数两次,一次又一次,在其默认线程 MainThread 中。

Python中多线程和多处理的区别

第4部分: 线程可以加速 CPU 密集型任务吗?

# Code snippet for Part 4
t1 = Thread(target = cpu_bound, args =(COUNT, ))
t2 = Thread(target = cpu_bound, args =(COUNT, ))
t1.start()
t2.start()
t1.join()
t2.join()
Java

上面刚刚证明了线程对于多个 IO-bound 任务非常有效。让我们使用相同的方法来执行我们的 CPU 密集型任务。它最初确实同时启动了我们的线程,但最后,我们看到整个程序执行花费了大约 40 秒!这是因为当 Thread-1 启动时,它获得了阻止 Thread-2 使用 CPU 的全局解释器锁 (GIL)。因此,Thread-2 必须等待 Thread-1 完成其任务并释放锁,以便它可以获取锁并执行其任务。这种锁的获取和释放增加了总执行时间的开销。因此,我们可以肯定地说,对于需要 CPU 处理某事的任务,线程并不是一个理想的解决方案。

Python中多线程和多处理的区别

第5部分: 那么,将任务拆分为单独的进程是否有效?

# Code snippet for Part 5
p1 = Process(target = cpu_bound, args =(COUNT, ))
p2 = Process(target = cpu_bound, args =(COUNT, ))
p1.start()
p2.start()
p1.join()
p2.join()
Java

让我们切入正题。在这里,MainProcess 启动了两个具有不同 PID 的子进程,每个子进程都负责将数字减少到零。每个进程并行运行,使用单独的 CPU 内核和自己的 Python 解释器实例,因此整个程序执行只用了 12 秒。请注意,输出可能以无序方式打印,因为进程彼此独立。每个进程在其自己的默认线程 MainThread 中执行该函数。在程序执行期间打开任务管理器。可以看到 Python 解释器的 3 个实例,MainProcess、Process-1 和 Process-2 各一个。还可以看到,在程序执行期间,两个子进程的 Power Usage 为“非常高”,因为它们正在执行的任务实际上对它们自己的 CPU 内核造成了损失,如 CPU 性能图中的峰值所示:

Python中多线程和多处理的区别

Python中多线程和多处理的区别

Python中多线程和多处理的区别

第6部分: 让我们为我们的 IO 绑定任务使用多处理……

# Code snippet for Part 6
p1 = Process(target = io_bound, args =(SLEEP, ))
p2 = Process(target = io_bound, args =(SLEEP, ))
p1.start()
p2.start()
p1.join()
p2.join()
Java

既然我们对多处理帮助我们实现并行性有了一个公平的想法,我们将尝试使用这种技术来运行 IO-bound 任务。我们确实观察到结果是非凡的,就像在多线程的情况下一样。由于进程 Process-1 和 Process-2 正在执行要求自己的 CPU 内核空闲几秒钟的任务,因此没有发现高功耗。但是创建进程本身是一项 CPU 繁重的任务,并且比创建线程需要更多的时间。此外,进程需要比线程更多的资源。因此,将多处理作为 IO 绑定任务的第二个选项总是更好,而多线程是第一个选项。

Python中多线程和多处理的区别

Python中多线程和多处理的区别

Python中多线程和多处理的区别

我们看到了六种不同的方法来执行一项任务,大约需要 10 秒,具体取决于 CPU 上的任务是轻还是重。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程

登录

注册