Python 中的线程锁
本教程将讨论在 Python 中使用线程锁的不同方法。
Python 中的竞争条件
竞争条件是当多个线程尝试修改同一个共享变量时出现的问题。所有线程同时从共享变量中读取相同的值。然后,所有线程尝试修改共享变量的值。但是,该变量最终只会存储最后一个线程的值,因为它会覆盖前一个线程写入的值。从这个意义上说,所有线程之间存在竞争,以查看最终哪个线程修改了变量的值。以下代码中的示例演示了这种现象。
from threading import Thread
counter = 0
def increase(by):
global counter
local_counter = counter
local_counter += by
counter = local_counter
print(f'counter={counter}')
t1 = Thread(target=increase, args=(10,))
t2 = Thread(target=increase, args=(20,))
t1.start()
t2.start()
t1.join()
t2.join()
print(f'The final counter is {counter}')
输出:
counter=10
counter=20
The final counter is 20
我们有一个全局共享变量 counter = 0
和两个线程 t1 和 t2。线程 t1 尝试将 counter 的值增加 10,线程 t2 尝试将 counter 的值增加 20。在上面的代码中,我们同时运行两个线程并尝试修改值计数器。根据上述逻辑,counter 的最终值应该是 30。但是,由于竞争条件,counter 要么是 10,要么是 20。
Python 中的线程锁
线程锁用于防止竞争条件。线程锁在被一个线程使用时锁定对共享变量的访问,以便任何其他线程无法访问它,然后在线程不使用共享变量时移除锁,以便其他线程可以使用该变量进行处理。threading 模块中的 Lock class 用于在 Python 中创建线程锁。acquire() 方法用于锁定对共享变量的访问,而 release()
方法用于解锁锁定。如果在未锁定的锁上使用 release()
方法会引发 RuntimeError 异常。
from threading import Thread, Lock
counter = 0
def increase(by, lock):
global counter
lock.acquire()
local_counter = counter
local_counter += by
counter = local_counter
print(f'counter={counter}')
lock.release()
lock = Lock()
t1 = Thread(target=increase, args=(10, lock))
t2 = Thread(target=increase, args=(20, lock))
t1.start()
t2.start()
t1.join()
t2.join()
print(f'The final counter is {counter}')
输出:
counter=10
counter=30
The final counter is 30
我们创建了一个全局共享变量 counter=0
和两个线程 t1 和 t2。两个线程都针对相同的 increase()
函数。increase(by, lock)
函数有两个参数。第一个参数是它将增加 counter 的数量,第二个参数是 Lock 类的实例。除了前面的声明,我们还在 Python 的 threading 模块中创建了一个 Lock 类的实例 lock。increase(by, lock)
函数中的这个 lock 参数使用 lock.acquire()
函数锁定对 counter 变量的访问,同时它被任何线程修改,并使用 lock 解锁锁定。release() 函数,当一个线程修改了 counter 变量。线程 t1 将 counter 的值增加 10,线程 t2 将 counter 的值增加 20。 由于线程锁定,不会发生竞争条件,最终 counter 的值为 30。
在 Python 中使用 with lock: 的线程锁
前一种方法的问题在于,当一个线程完成处理时,我们必须小心地解锁每个锁定的变量。如果没有正确完成,我们的共享变量将只会被第一个线程访问,而其他线程将无法访问共享变量。这个问题可以通过使用上下文管理来避免。我们可以使用 with lock: 并将我们所有的关键代码放在这个块中。这是防止竞争条件的更简单的方法。以下代码片段显示了使用 with lock: 来防止 Python 中的竞争条件。
from threading import Thread, Lock
counter = 0
def increase(by, lock):
global counter
with lock:
local_counter = counter
local_counter += by
counter = local_counter
print(f'counter={counter}')
lock = Lock()
t1 = Thread(target=increase, args=(10, lock))
t2 = Thread(target=increase, args=(20, lock))
t1.start()
t2.start()
t1.join()
t2.join()
print(f'The final counter is {counter}')
输出:
counter=10
counter=30
The final counter is 30
我们将增加 counter 的代码放在 with lock: 块中。线程 t1 将 counter 的值增加 10,线程 t2 将 counter 的值增加 20。没有发生竞争条件,counter 的最终值为 30。此外,我们不需要担心解锁线程锁。
两种方法都完美地完成了它们的工作,即两种方法都可以防止竞争条件的发生,但是第二种方法远远优于第一种,因为它可以避免我们为处理线程锁的锁定和解锁而头疼。与第一种方法相比,它编写起来也更简洁,更易于阅读。
注: 本文转载自:delftstack 。对于文中示例已都进行过运行。如遇到错误代码会进行相应的修改,修改正确之后才会在文中发布。如大家在运行示例中发现有代码错误的,请及时告知。
相关文章
Python for 循环中的下一项
发布时间:2023/04/26 浏览次数:179 分类:Python
-
本文讨论了 Python 中的 for 循环以及如何通过使用 for 循环和示例来跳过列表的第一个元素。
Python While 循环用户输入
发布时间:2023/04/26 浏览次数:148 分类:Python
-
我们可以在 while 循环中使用 input() 函数来输入数据,直到在 Python 中满足某个条件。
在 Python 中将整数转换为罗马数字
发布时间:2023/04/26 浏览次数:87 分类:Python
-
本篇文章将介绍在 Python 中将整数转换为罗马数字。以下是一个 Python 程序的实现,它将给定的整数转换为其等效的罗马数字。
在 Python 中将罗马数字转换为整数
发布时间:2023/04/26 浏览次数:144 分类:Python
-
本文讨论如何在 Python 中将罗马数字转换为整数。 我们将使用 Python if 语句来执行此操作。 我们还将探讨在 Python 中将罗马数字更改为整数的更多方法。
在 Python 中读取 gzip 文件
发布时间:2023/04/26 浏览次数:70 分类:Python
-
本篇文章强调了压缩文件的重要性,并演示了如何在 Python 中使用 gzip 进行压缩和解压缩。
在 Python 中锁定文件
发布时间:2023/04/26 浏览次数:141 分类:Python
-
本文解释了为什么在 Python 中锁定文件很重要。 这讨论了当两个进程在没有锁的情况下与共享资源交互时会发生什么的示例,为什么在放置锁之前知道文件状态很重要,等等
在 Python 中将 PDF 转换为文本
发布时间:2023/04/26 浏览次数:196 分类:Python
-
在本教程中,我们将学习如何使用 Python 使用 PyPDF2、Aspose 和 PDFminer 将 PDF 文档转换为文本文件。
在 Python 中创建临时文件
发布时间:2023/04/26 浏览次数:53 分类:Python
-
本文讲解了tempfile库函数的四个子函数:TemporaryFile、NamedTemporaryFile、mkstemp、TemporaryDirectory。 每个部分都提供了适当的程序,以简化对概念的理解。