Python | 线程锁 | 5分钟掌握【同步锁】(Threading.Lock)

概念

threading.Lock 同步锁,可以用于保证多个线程对共享数据的独占访问。

当一个线程获取了锁之后,其他线程在此期间将不能再次获取该锁,直到该线程释放锁。这样就可以保证共享数据的独占访问,从而避免数据不一致的问题;但是错误的使用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
############################################################

究其原因,我们可以假设:

  1. 当线程t1拿到counter=100时,还没来得及+1;
  2. 此时线程t2也拿到了counter=100。
  3. 现在当t1和t2对counter进行加1后,counter值都变成了101并返回给全局变量。
  4. 我们发现,两个线程在同时使用和修改同一全局变量counter时,总数少加了一个1。
  5. 因为一个线程在对公共资源做读写的时候,其它线程也能对它进行读写,两个线程拿到了一样的值,这就导致了最后产生的结果数据不一致。

为了防止这个问题,我们对公共资源进行使用时,一定要进行加锁保护;

加锁

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
############################################################

因为加了锁,两个线程成功地对计数器分别进行了递增操作。

究其原因,我们可以假设:

  1. 当线程t1先获取到了锁lock,并对数据处理部分进行了加锁,假设t1拿到counter=1;
  2. 此时线程t2也想拿counter,但是因为获取不到锁lock,就只能等待lock被释放。
  3. 当线程t1完全执行完递增操作后,将锁lock给释放掉了;
  4. 此时t2突然发现它能获取到锁lock了,于是就对t2的数据处理部分进行了加锁,然后执行数据递增逻辑。
  5. 直到t2的完全执行完递增操作后,将锁lock释放;
  6. 我们发现,两个线程由于锁的作用,只能顺序性的获取需要的公共资源counter,保证了线程流程执行中公共资源使用的独占性。

因为加锁而且没有出现竞态,所以结果是准确且符合预期;
但同时,在多线程中错误的加锁顺序可能会导致锁等待,也就是俗称的“死锁"现象。


死锁

死锁指的是在多线程或分布式系统中,两个或多个线程或进程因互相等待对方释放资源而陷入无限等待的状态,无法继续执行的情况。

当多个线程或进程互相竞争同一组资源时,如果每个线程都持有一些资源,并且都在等待另一个线程释放它所需要的资源时,就会发生死锁。这种情况下,所有的线程都被阻塞,无法继续执行,从而导致系统出现僵死状态。

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
...无尽的等待
############################################################

在上面的代码中,我们创建了两个Lock对象,并在两个线程中使用了不同的顺序来获取这两个锁。
线程1首先获取了lock1,然后尝试获取lock2;而线程2则首先获取了lock2,然后尝试获取lock1。
由于两个线程获取锁的顺序不同,它们【可能】会在某个时刻同时持有一个锁,但尝试获取另一个锁时会被阻塞。

例如,当线程1获取了lock1,而线程2获取了lock2时,它们都会等待另一个锁的释放,从而导致死锁。
这时候程序将不再继续执行,而是一直处于等待状态。

这里如果想要要避免死锁,就需要确保不同线程获取锁的顺序是一致的。
也就是说,如果一个线程首先获取了lock1,那么它在尝试获取lock2时也应该按照相同的顺序来获取,而不是反过来。


解决死锁

死锁的产生原因通常是由于多个线程对共享资源的竞争,同时又没有良好的资源分配策略或锁的获取顺序导致的。在设计多线程或分布式系统时,避免死锁是一个重要的问题。常用的避免死锁的方法包括:

  1. 加锁顺序的规范化
  2. 资源分配策略的优化
  3. 使用超时等待等机制。



🎉如果对你有所帮助,可以点赞、关注、收藏起来,不然下次就找不到了🎉


【点赞】⭐️⭐️⭐️⭐️⭐️
【关注】⭐️⭐️⭐️⭐️⭐️
【收藏】⭐️⭐️⭐️⭐️⭐️

Thanks for watching.
Kenny

`Threading.Lock` 是 Python 中 `threading` 模块中的一个组件,用于同步线程执行。在并发编程中,锁(Lock)是一种基本的同步机制,它确保同一时间只有一个线程能够访问共享资源。当一个线程获得了锁后,其他尝试获取该锁的线程会被阻塞,直到锁被释放。 以下是一些关于 `threading.Lock` 的关键点: 1. **获得锁**:使用 `lock.acquire()` 方法获取锁,如果锁已被其他线程持有,则当前线程会被阻塞等待。 2. **释放锁**:一旦完成对共享资源的操作,应使用 `lock.release()` 方法释放锁,这使得其他线程有机会获得并执行。 3. **重入锁**:`Lock` 类默认不支持重入(即一个已经持有锁的线程再次获得同一个锁)。如果你需要可重入的锁,可以使用 `threading.RLock`。 4. **异常处理**:在 Python 中,通常建议在 `with` 语句中使用锁,这样可以确保即使在操作过程中发生异常,锁也会自动释放。 ```python import threading # 创建一个锁实例 lock = threading.Lock() def thread_function(): with lock: # 这里是临界区,只有当没有其他线程拥有锁时才能执行 print("Thread {} is executing".format(threading.current_thread().name)) # 创建两个线程 t1 = threading.Thread(target=thread_function, name="Thread 1") t2 = threading.Thread(target=thread_function, name="Thread 2") # 启动线程 t1.start() t2.start() t1.join() # 确保主线程等待 t1 完成后再进行下一行 t2.join() ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

比特本特

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值