深入理解Python中的threading.Lock同步锁与无锁机制在多线程中的应用
文章目录

概念
螺纹锁定锁可用于确保多个线程对共享数据的独家访问。
当线程获取锁定时,其他线程将无法在此期间再次获取锁,直到线释放锁为止。这样可以确保对共享数据的独家访问,从而避免了数据不一致的问题;但是,锁的使用不正确也会导致僵局等待;
无锁
当使用多线程访问和修改公共资源时,无锁定会导致数据与预期结果不同;
import threading
# 创建一个Lock对象
lock = threading.Lock()
# 共享资源
counter = 0
# 线程函数
def worker():
global counter
# 获取锁
# 对共享资源进行操作
for i in range(1000000):
counter += 1
print("Counter value: ", counter)
# 创建两个线程并启动它们
t1 = threading.Thread(target=worker)
t2 = threading.Thread(target=worker)
t1.start()
t2.start()
t1.join()
t2.join()
######################## 结果 ############################
Counter value: 1264715
Counter value: 1326630
Process finished with exit code 0
############################################################
要找出原因,我们可以假设:
当线程T1计数器= 100时,+1还为时不晚;目前,线程T2还可以计数器= 100。现在,当T1和T2向计数器加1时,计数器值将变为101并将其返回到全局变量。我们发现,当两个线程同时使用和修改相同的全局变量计数器时,总数会逐一增加。因为当一个线程读取并写入公共资源时,其他线程也可以读写并写入它,并且两个线程获得相同的值,从而导致最终结果数据中产生的结果不一致。
为了防止此问题,我们必须在使用公共资源时使用锁定保护;
添加锁
import threading
# 创建一个Lock对象
lock = threading.Lock()
# 共享资源
counter = 0
# 线程函数
def worker():
global counter
# 获取锁
lock.acquire()
try:
# 对共享资源进行操作
for i in range(1000000):
counter += 1
print("Counter value: ", counter)
finally:
# 释放锁
lock.release()
# 创建两个线程并启动它们
t1 = threading.Thread(target=worker)
t2 = threading.Thread(target=worker)
t1.start()
t2.start()
t1.join()
t2.join()
######################## 结果 #############################
Counter value: 1000000
Counter value: 2000000
Process finished with exit code 0
############################################################
由于锁定,这两个线程成功地增加了计数器。
要找出原因,我们可以假设:
当Thread T1首先获取锁定锁并锁定数据处理部分时,假设T1获得Counter = 1;目前,Thread T2还希望获得反击,但是由于无法获得锁定锁,因此只能等待锁定锁定。当线程T1完成增量操作时,锁定锁定;此时,T2突然发现它可以获取锁定锁,因此它锁定了T2的数据处理部分,然后执行数据增量逻辑。在T2中完全执行增量操作之前,锁定锁定为锁。我们发现,由于锁定,这两个线程只能顺序获得所需的公共资源计数器,从而确保在线程流执行中使用公共资源的排他性。 。
由于锁定和没有竞争发生,因此结果是准确的,并且与期望一致。
但与此同时,多线程中错误的锁定顺序可能导致锁定等待,这通常称为“僵局”。
僵局
僵局是指两个或多个线程或过程被困在无限状态的情况下,彼此等待释放资源,并且无法继续在多线程或分布式系统中执行。
当多个线程或流程相互竞争相同的资源集时,如果每个线程拥有一些资源并正在等待另一个线程发布所需的资源,则会发生僵局。在这种情况下,所有线程都被阻止并且无法继续执行,从而导致系统中的僵尸状态。
import threading
# 创建两个Lock对象
lock1 = threading.Lock()
lock2 = threading.Lock()
# 线程函数1
def worker1():
lock1.acquire()
print("Worker 1 acquired lock 1")
lock2.acquire()
print("Worker 1 acquired lock 2")
lock2.release()
print("Worker 1 released lock 2")
lock1.release()
print("Worker 1 released lock 1")
# 线程函数2
def worker2():
lock2.acquire()
print("Worker 2 acquired lock 2")
lock1.acquire()
print("Worker 2 acquired lock 1")
lock1.release()
print("Worker 2 released lock 1")
lock2.release()
print("Worker 2 released lock 2")
# 创建两个线程并启动它们
t1 = threading.Thread(target=worker1)
t2 = threading.Thread(target=worker2)
t1.start()
t2.start()
t1.join()
t2.join()
######################## 结果 ############################
Worker 1 acquired lock 1
Worker 2 acquired lock 2
...无尽的等待
############################################################
在上面的代码中,我们创建两个锁定对象,并在两个线程中使用不同的订单来获取两个锁。
线程1首先获得锁定1,然后尝试获得锁2;当Thread 2首先获得Lock2,然后尝试获得Lock1。
由于这两个线程以不同的顺序获取锁,因此它们[可能]在某个时刻可以握住一个锁,但是在尝试获取另一个锁定时将被阻止。
例如,当线程1获取Lock1和Thread 2获取Lock2时,它们都在等待另一个锁定释放,从而导致僵局。
目前,该程序将不再继续执行,而是处于等待状态。
如果您想在这里避免僵局,则需要确保不同线程获取锁的顺序是一致的。
也就是说,如果线程首先获得锁定,则在尝试获取lock2而不是相反的情况下,它也应以相同的顺序获得。
解决僵局
僵局的原因通常是由竞争共享资源竞争的多个线程引起的,而没有良好的资源分配策略或锁定收购订单。在设计多线程或分布式系统时,避免僵局是一个重要的问题。避免僵局的常用方法包括:
锁定订单的标准化资源分配策略的优化使用超时等待和其他机制。