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。此外,我们不需要担心解锁线程锁。
两种方法都完美地完成了它们的工作,即两种方法都可以防止竞争条件的发生,但是第二种方法远远优于第一种,因为它可以避免我们为处理线程锁的锁定和解锁而头疼。与第一种方法相比,它编写起来也更简洁,更易于阅读。
Maisam is a highly skilled and motivated Data Scientist. He has over 4 years of experience with Python programming language. He loves solving complex problems and sharing his results on the internet.
LinkedIn