Python中的多线程[带有编码示例]

已发表: 2020-11-30

掌握 Python 基础知识后,下一步就是改进和加快代码速度。 多线程是使用“线程”实现优化的一种方式。 这些线程是什么? 这些与流程有何不同? 让我们来了解一下。

在本教程结束时,您将掌握以下知识:

  • 什么是线程和进程?
  • 多线程是如何实现的?
  • 它的局限性是什么?
  • 何时使用多线程?

学习世界顶尖大学的在线数据科学课程获得行政 PG 课程、高级证书课程或硕士课程,以加快您的职业生涯。

目录

Python 中的线程

当我们想到多任务时,我们会想到并行执行。 多线程不是严格的并行执行。 线程可以被认为是独立运行的程序不同部分的执行流的独立实体。 所以本质上,线程不是并行执行的,但是 Python 从一个线程切换到另一个线程的速度如此之快,以至于它们看起来是并行的。

另一方面,进程是严格并行的,并在不同的内核上运行以实现更快的执行。 线程也可以在不同的处理器上运行,但从技术上讲,它们仍然不会并行运行。

您是否在考虑如果线程不并行运行,那么它们如何使事情变得更快? 答案是它们并不总是使处理速度更快。 多线程专门用于线程将加快处理速度的任务。

线程的所有信息都包含在线程控制块(TCB)中。 TCB由以下主要部分组成:

  1. 唯一的 TID – 线程标识符
  2. 堆栈指针,指向进程中线程的堆栈
  3. 程序计数器,存储线程当前正在执行的指令的地址
  4. 线程状态(运行、就绪、等待、开始或完成)

话虽如此,进程中可以包含多个线程,它们共享代码、数据和所有文件。 并且所有线程都有自己独立的寄存器和堆栈,它们可以访问。

现在您可能想知道,如果线程使用公共数据和代码,它们如何在不妨碍其他线程的情况下都使用它。 这是我们将在本教程后面讨论的多线程的最大限制。

上下文切换

现在如上所述,线程不会并行运行,而是并行运行。 所以当一个线程 T1 开始执行时,所有其他线程都处于等待模式。 只有在 T1 完成执行后,任何其他排队线程才能开始执行。 Python 从一个线程切换到另一个线程的速度如此之快,以至于看起来像是并行执行。 这种切换就是我们所说的“上下文切换”。

多线程编程

考虑下面的代码,它使用线程来执行立方体和平方运算。

导入线程

def立方(n) :
打印( “立方体:{}” .format(n * n * n))

def平方(n)
打印( “正方形:{}” .format(n * n))

如果__name__ == “__main__”
# 创建线程
t1 = threading.Thread(target=squarer, args=( 5 ,))
t2 = threading.Thread(target=cuber, args=( 5 ,))

# 启动线程 t1
t1.start()
# 启动线程 t2
t2.start()

# 等到t1完成
t1.join()
# 等到t2完成
t2.join()

# 两个线程都完成了
打印( “完成!”

#输出:
广场: 25
立方体: 125
完毕!

现在让我们尝试理解代码。

首先,我们导入负责所有任务的线程模块。 在 main 内部,我们通过创建 Thread 类的子类来创建 2 个线程。 我们需要传递目标,即需要在该线程中执行的函数,以及需要传递给这些函数的参数。

现在一旦声明了线程,我们就需要启动它们。 这是通过在线程上调用start方法来完成的。 一旦启动,主程序需要等待线程完成它们的处理。 我们使用wait方法让主程序暂停并等待线程 T1 和 T2 完成它们的执行。

必读:初学者的 Python 挑战

线程同步

正如我们上面所讨论的,线程不会并行执行,而是 Python 从一个切换到另一个。 因此,线程之间的正确同步非常关键,以避免任何奇怪的行为。

比赛条件

同一进程下的线程使用公共数据和文件,这可能导致多个线程之间的数据“竞争”。 因此,如果一条数据被多个线程访问,它会被两个线程都修改,我们得到的结果不会像预期的那样。 这称为竞争条件。

因此,如果您有两个线程可以访问相同的数据,那么它们都可以在该特定线程执行时访问和修改它。 所以当 T1 开始执行并修改一些数据时,T2 处于睡眠/等待模式。 然后 T1 停止执行并进入睡眠模式,将控制权交给 T2,T2 也可以访问相同的数据。 所以 T2 现在将修改和覆盖相同的数据,这将在 T1 再次开始时导致问题。

线程同步的目的是确保这种竞争条件永远不会出现,并且代码的关键部分由线程以同步方式一次访问。

锁具

为了解决和防止 Race Condition 及其后果,thread 模块提供了一个Lock类,它使用 Semaphores 来帮助线程同步。 信号量只不过是二进制标志。 将它们视为电话亭上的“参与”标志,其值为“参与”(等于 1)或“未参与”(等于 0)。 所以每次线程遇到带锁的代码段时,它必须检查锁是否已经处于 1 状态。 如果是,那么它必须等到它变为 0 才能使用它。

Lock有两个主要方法:

  1. acquire([blocking]) acquire方法接受参数阻塞作为TrueFalse 如果线程 T1 的锁以阻塞为 True 启动,它将等待或保持阻塞,直到代码的关键部分被另一个线程 T2 锁定。 一旦另一个线程 T2 释放锁,线程 T1 获取锁并返回True

另一方面,如果线程 T1 的锁定是在参数阻塞为False的情况下启动的,如果临界区已经被线程 T2 锁定,线程 T1 将不会等待或保持阻塞。 如果它认为它已锁定,它将直接返回 False 并退出。 但是,如果代码没有被另一个线程锁定,它将获取锁定并返回True

release() :当在锁上调用 release 方法时,它会解锁锁并返回 True。 此外,它还将检查是否有任何线程正在等待释放锁。 如果有,那么它将允许其中一个人访问锁。

但是,如果锁已解锁,则会引发 ThreadError。

死锁

当我们处理多个锁时出现的另一个问题是——死锁。 当线程由于各种原因没有释放锁时,就会发生死锁。 让我们考虑一个简单的示例,我们执行以下操作:

导入线程

l = threading.Lock()
# 第一次获取之前
l.acquire()
# 在第二次获取之前
l.acquire()
# 现在获得了两次锁

在上面的代码中,我们调用了两次acquire方法,但在第一次获取后不释放它。 因此,当 Python 看到第二个获取语句时,它将无限期地进入等待模式,因为我们从未释放过前一个锁。

这些死锁条件可能会在您没有意识到的情况下潜入您的代码中。 即使您包含释放调用,您的代码也可能在中途失败,并且永远不会调用释放并且锁将保持锁定状态。 克服这个问题的一种方法是使用with as语句,也称为上下文管理器。 使用with - as语句,一旦处理结束或由于任何原因失败,锁将自动释放。

阅读: Python 项目理念和主题

在你走之前

正如我们之前所讨论的,多线程并不是在所有应用程序中都有用,因为它并没有真正让事情并行运行。 但是多线程的主要应用是在 I/O 任务期间,CPU 在等待数据加载时处于空闲状态。 多线程在这里起着至关重要的作用,因为 CPU 的空闲时间被用于其他任务,从而使其成为优化的理想选择。

如果您想了解数据科学,请查看 IIIT-B 和 upGrad 的数据科学执行 PG 计划,该计划是为在职专业人士创建的,提供 10 多个案例研究和项目、实用的实践研讨会、与行业专家的指导、1与行业导师一对一,400 多个小时的学习和顶级公司的工作协助。

Python中的线程是什么?

线程是进程中的实体,可以安排在 Python 中执行。 通俗的讲,线程是计算机执行的计算过程。 它是程序中的一组此类指令,开发人员可以独立于其他脚本运行。 线程允许您通过使用并行性来提高应用程序速度。 这是一个轻量级进程,可以让任务并行运行。 线程独立运行并最大限度地利用 CPU,从而提高 CPU 性能。

Python中的多线程有什么用?

多线程是 Python 编程中的一种线程技术,它允许多个线程在 CPU 的帮助下通过线程之间的快速切换(称为上下文切换)来并发操作。 当我们可以将任务分成多个单独的部分时,我们使用多线程。 例如,假设您需要执行复杂的数据库查询以获取数据并将该查询分解为多个单独的查询。 在这种情况下,最好为每个查询分配一个线程并并行运行它们。

什么是线程同步?

线程同步被描述为一种保证两个或多个并发进程或线程不会同时执行程序的关键部分的方法。 同步方法用于控制进程对重要部分的访问。 当我们在一个程序中启动两个或多个线程时,有可能多个线程可能会尝试访问同一个资源,从而由于并发挑战而导致意外结果。 例如,如果许多线程尝试在同一个文件中写入,则数据可能会损坏,因为其中一个线程可以覆盖数据,或者当一个线程正在打开而另一个线程正在关闭同一个文件时。