Python 中的多线程示例:在 Python 中学习 GIL
python 编程语言允许您使用多处理或多线程。在本教程中,您将学习如何使用 Python 编写多线程应用程序。
什么是线程?
线程是并发编程的执行单元。多线程是一种允许 CPU 同时执行一个进程的多个任务的技术。这些线程可以单独执行,同时共享它们的进程资源。
什么是进程?
进程基本上是正在执行的程序。当您在计算机中启动应用程序(如浏览器或文本编辑器)时,操作系统会创建一个 进程。
什么是 Python 中的多线程?
Python 中的多线程 编程是一种众所周知的技术,其中一个进程中的多个线程与主线程共享它们的数据空间,这使得线程内的信息共享和通信变得容易和高效。线程比进程轻。多线程可以单独执行,同时共享它们的进程资源。多线程的目的是同时运行多个任务和函数单元。
在本教程中,您将学习,
- 什么是线程?
- 什么是流程?
- 什么是多线程?
- 什么是多处理?
- Python 多线程与多处理
- 为什么要使用多线程?
- Python 多线程
- 线程和线程模块
- 线程模块
- 线程模块
- 死锁和竞争条件
- 同步线程
- 什么是 GIL?
- 为什么需要 GIL?
什么是多处理?
多处理允许您同时运行多个不相关的进程。这些进程不共享资源并通过 IPC 进行通信。
Python 多线程与多处理
要了解进程和线程,请考虑以下情况:计算机上的 .exe 文件是一个程序。当您打开它时,操作系统会将其加载到内存中,然后 CPU 会执行它。现在正在运行的程序的实例称为进程。
每个流程都有两个基本组成部分:
- 守则
- 数据
现在,一个进程可以包含一个或多个称为线程的子部分。 这取决于操作系统架构。您可以将线程视为可以由操作系统单独执行的进程的一部分。
换句话说,它是一个可以由操作系统独立运行的指令流。单个进程中的线程共享该进程的数据,并旨在协同工作以促进并行性。
为什么要使用多线程?
多线程允许您将应用程序分解为多个子任务并同时运行这些任务。如果你正确使用多线程,你的应用程序速度、性能和渲染都可以得到提高。
Python 多线程
Python 支持多处理和多线程的构造。在本教程中,您将主要关注实现 多线程 使用 python 的应用程序。有两个主要模块可用于处理 Python 中的线程:
- 线程 模块,以及
- 线程 模块
但是,在 python 中,还有一种叫做全局解释器锁 (GIL) 的东西。它不会带来太多的性能提升,甚至可能降低 一些多线程应用程序的性能。您将在本教程的后续部分中了解有关它的所有内容。
线程和线程模块
您将在本教程中学习的两个模块是 线程模块 和线程模块 .
但是,线程模块早已被弃用。从 Python 3 开始,它已被指定为过时的并且只能作为 __thread 访问 为了向后兼容。
您应该使用更高级别的 threading 您打算部署的应用程序的模块。此处仅出于教育目的介绍线程模块。
线程模块
使用该模块创建新线程的语法如下:
thread.start_new_thread(function_name, arguments)
好的,现在您已经了解了开始编码的基本理论。因此,打开您的 IDLE 或记事本并输入以下内容:
import time import _thread def thread_test(name, wait): i = 0 while i <= 3: time.sleep(wait) print("Running %s\n" %name) i = i + 1 print("%s has finished execution" %name) if __name__ == "__main__": _thread.start_new_thread(thread_test, ("First Thread", 1)) _thread.start_new_thread(thread_test, ("Second Thread", 2)) _thread.start_new_thread(thread_test, ("Third Thread", 3))
保存文件并按 F5 运行程序。如果一切都正确完成,这就是您应该看到的输出:
您将在接下来的部分中了解有关竞态条件以及如何处理它们的更多信息
代码说明
- 这些语句导入时间和线程模块,用于处理 Python 线程的执行和延迟。
- 在这里,您定义了一个名为 thread_test 的函数, 这将由 start_new_thread 调用 方法。该函数运行一个while循环进行四次迭代并打印调用它的线程的名称。迭代完成后,它会打印一条消息,说明线程已完成执行。
- 这是程序的主要部分。在这里,您只需调用 start_new_thread thread_test 的方法 函数作为参数。这将为您作为参数传递的函数创建一个新线程并开始执行它。请注意,您可以替换此(线程_ test) 与您想作为线程运行的任何其他函数。
线程模块
这个模块是python中线程的高级实现,也是管理多线程应用程序的事实标准。与线程模块相比,它提供了广泛的功能。
以下是该模块中定义的一些有用函数的列表:
函数名 | 说明 |
---|---|
activeCount() | 返回线程数 还活着的对象 |
currentThread() | 返回 Thread 类的当前对象。 |
枚举() | 列出所有活动的线程对象。 |
isDaemon() | 如果线程是守护进程,则返回 true。 |
isAlive() | 如果线程还活着,则返回 true。 |
线程类方法 | |
开始() | 启动线程的活动。每个线程只能调用一次,因为多次调用会抛出运行时错误。 |
运行() | 此方法表示线程的活动,并且可以被扩展 Thread 类的类覆盖。 |
join() | 它会阻止其他代码的执行,直到调用 join() 方法的线程终止。 |
背景故事:线程类
在开始使用threading模块编写多线程程序之前,了解Thread类至关重要。thread类是python中定义模板和线程操作的主要类。
创建多线程 python 应用程序最常见的方法是声明一个扩展 Thread 类并覆盖它的 run() 方法的类。
总而言之,Thread 类表示在单独的 thread 中运行的代码序列 控制权。
因此,在编写多线程应用程序时,您将执行以下操作:
- 定义一个扩展 Thread 类的类
- 覆盖 __init__ 构造函数
- 重写 run() 方法
一旦创建了一个线程对象,start() 方法可用于开始执行此活动和 join() 方法可用于阻塞所有其他代码,直到当前活动完成。
现在,让我们尝试使用 threading 模块来实现您之前的示例。再次启动你的 IDLE 并输入以下内容:
import time import threading class threadtester (threading.Thread): def __init__(self, id, name, i): threading.Thread.__init__(self) self.id = id self.name = name self.i = i def run(self): thread_test(self.name, self.i, 5) print ("%s has finished execution " %self.name) def thread_test(name, wait, i): while i: time.sleep(wait) print ("Running %s \n" %name) i = i - 1 if __name__=="__main__": thread1 = threadtester(1, "First Thread", 1) thread2 = threadtester(2, "Second Thread", 2) thread3 = threadtester(3, "Third Thread", 3) thread1.start() thread2.start() thread3.start() thread1.join() thread2.join() thread3.join()
当你执行上面的代码时,这将是输出:
代码说明
- 这部分与我们之前的示例相同。在这里,您导入用于处理 Python 线程的执行和延迟的时间和线程模块。
- 在这里,您将创建一个名为 threadtester 的类,它继承或扩展了 Thread 线程模块的类。这是在 python 中创建线程的最常见方法之一。但是,您应该只覆盖构造函数和 run() 应用程序中的方法。正如您在上面的代码示例中看到的,__init__ 方法(构造函数)已被覆盖。同样,您还覆盖了 run() 方法。它包含您要在线程内执行的代码。在本例中,您调用了 thread_test() 函数。
- 这是 thread_test() 方法,它采用 i 的值 作为参数,在每次迭代时将其减 1,然后循环其余代码,直到 i 变为 0。在每次迭代中,它打印当前执行线程的名称并休眠等待秒(这也被用作参数)。
- thread1 =threadtester(1, “First Thread”, 1) 在这里,我们正在创建一个线程并传递我们在 __init__ 中声明的三个参数。第一个参数是线程的id,第二个参数是线程的名字,第三个参数是计数器,它决定了while循环应该运行多少次。
- thread2.start()start方法用于启动线程的执行。在内部,start() 函数调用类的 run() 方法。
- thread3.join() join() 方法会阻塞其他代码的执行,并等待调用它的线程完成。
如您所知,同一进程中的线程可以访问该进程的内存和数据。因此,如果多个线程同时尝试更改或访问数据,则可能会出现错误。
在下一节中,您将看到当线程访问数据和临界区而不检查现有访问事务时可能出现的各种复杂情况。
死锁和竞争条件
在了解死锁和竞争条件之前,了解一些与并发编程相关的基本定义会很有帮助:
- Critical Section是访问或修改共享变量的代码片段,必须作为原子事务执行。
- 上下文切换是 CPU 在从一个任务切换到另一个任务之前存储线程状态的过程,以便以后可以从同一点恢复。
死锁
死锁是开发人员在 python 中编写并发/多线程应用程序时最担心的问题。理解死锁的最佳方法是使用经典的计算机科学示例问题,即餐饮哲学家问题。
哲学家用餐问题陈述如下:
如图所示,五位哲学家坐在一张圆桌上,桌上摆着五盘意大利面(一种意大利面)和五把叉子。
Python