三把锁和event事件

死锁和递归锁

【1】死锁#

def task(i):
    mutex_1.acquire()
    print(f'{current_thread().name}抢到了第一把锁')
    mutex_2.acquire()
    print(f'{current_thread().name}抢到了第二把锁')
    mutex_2.release()  # 释放锁二
    mutex_1.release()  # 释放锁一


    mutex_2.acquire()
    print(f'{current_thread().name}抢到了第二把锁')
    mutex_1.acquire()
    time.sleep(1)
    print(f'{current_thread().name}抢到了第一把锁')
    mutex_1.release()  # 释放锁一
    mutex_2.release()  # 释放锁二


if __name__ == '__main__':
    t_list = [Thread(target=task, args=(i,)) for i in range(1, 8)]
    for t in t_list:
        t.start()
  • 在这个例子中,我添加了两把锁(锁1,锁2),创建多个线程去抢锁
  • 仔细看这个例子,会发现这是个死锁
  1. 线程1拿到锁1
  2. 其他线程会被堵住,因为线程1没有释放锁1
  3. 线程1拿到锁2
  4. 其他线程会被堵住,因为线程1没有释放锁1
  5. 线程1释放锁2
  6. 其他线程会被堵住,因为线程1没有释放锁1
  7. 线程1释放锁1
  8. 其他线程会争夺锁1
  9. 线程1拿到锁2
  10. 拿到锁1的线程不会释放锁1,因为他没有拿到锁2
  11. 线程1不会拿到锁1,其他程序没有释放锁1
  12. 程序夯住,这就是死锁线程

【2】递归锁#

  • 递归锁(Recursive Lock)是一种特殊类型的锁,它允许同一个线程多次获取同一资源的锁定。在多线程环境中,当一个线程已经持有某个锁时,如果它再次尝试获取同一个锁,通常情况下会导致死锁。但如果这个锁是递归锁,线程可以再次获得锁而不会阻塞。
  • 递归锁内部维护了一个计数器和一个原始所有者线程的标识。当线程首次获取锁时,计数器会增加,锁的所有者被设置为当前线程。如果同一个线程再次获取锁,计数器会继续增加。每次线程释放锁时,计数器减一。只有当计数器回到零时,锁才会真正释放,其他线程才能获取它。
  • 递归锁在 Python 中可以通过 threading 模块中的 RLock 类实现。
from threading import Thread, RLock, current_thread

mutex = RLock()


def task(i):
    mutex.acquire()
    print(f'{current_thread().name}抢到了锁')
    mutex.acquire()
    print(f'{current_thread().name}抢到了锁')
    mutex.release()
    mutex.release()


if __name__ == '__main__':
    t_list = [Thread(target=task, args=(i,)) for i in range(1, 11)]
    for t in t_list:
        t.start()
'''
Thread-1 (task)抢到了锁
Thread-1 (task)抢到了锁
Thread-2 (task)抢到了锁
Thread-2 (task)抢到了锁
Thread-3 (task)抢到了锁
Thread-3 (task)抢到了锁
Thread-4 (task)抢到了锁
Thread-4 (task)抢到了锁
Thread-5 (task)抢到了锁
Thread-5 (task)抢到了锁
Thread-6 (task)抢到了锁
Thread-6 (task)抢到了锁
Thread-7 (task)抢到了锁
Thread-7 (task)抢到了锁
Thread-8 (task)抢到了锁
Thread-8 (task)抢到了锁
Thread-9 (task)抢到了锁
Thread-9 (task)抢到了锁
Thread-10 (task)抢到了锁
Thread-10 (task)抢到了锁

进程已结束,退出代码0

'''

【3】信号量#

  • 计数器: 信号量内部维护了一个计数器,这个计数器表示可用的资源数量。当计数器大于零时,表示有资源可用;当计数器为零时,表示没有可用资源。

  • 获取(Acquire)操作: 当一个线程尝试获取信号量时,如果计数器大于零,计数器就减一,线程获得资源的访问权限。如果计数器为零,线程会被阻塞,直到计数器变为大于零。

  • 释放(Release)操作: 当线程完成对资源的使用后,它必须释放信号量。释放操作会使计数器加一,表示资源又变得可用。如果有其他线程正在等待这个信号量,其中一个线程(或多个,取决于计数器增加的值)将被唤醒并获得资源访问权限。

  • 这里以一个停车场停车为例子

from threading import Thread, Semaphore
import time
import random


def task(semaphore, i):
    semaphore.acquire()
    print(f'宝马{i}号抢到了车位')
    time.sleep(random.randint(1, 3))
    semaphore.release()
    print(f'宝马{i}离开了车位')


if __name__ == '__main__':
    semaphore = Semaphore(5)
    t_list = [Thread(target=task, args=(semaphore, i)) for i in range(1, 11)]
    for t in t_list:
        t.start()

'''
宝马1号抢到了车位
宝马2号抢到了车位
宝马3号抢到了车位
宝马4号抢到了车位
宝马5号抢到了车位
宝马3离开了车位
宝马1离开了车位
宝马6号抢到了车位
宝马4离开了车位
宝马8号抢到了车位
宝马7号抢到了车位
宝马2离开了车位
宝马9号抢到了车位
宝马5离开了车位
宝马10号抢到了车位
宝马7离开了车位
宝马10离开了车位
宝马9离开了车位
宝马6离开了车位
宝马8离开了车位
进程已结束,退出代码0

'''

  • 前五个线程都会直接抢到这把锁,因为信号量为5,
  • 之后就会释放一个锁进一个

【4】event事件#

  • event事件可以让一个子进程或者子线程等待另一个子进程或者子线程执行完毕再执行

  • 相当于join()方法可以让主进程等待调用join()方法的子线程执行完毕再执行。

  • Event 对象在 Python 的 threading 模块中提供,它有以下几个关键的特性和方法:

  1. 状态标志: Event 对象内部维护一个内部标志,它可以被设置为 TrueFalse。默认情况下,这个标志为 False
  2. set() 方法: 当调用 set() 方法时,Event 对象的内部标志被设置为 True。这通常表示某个特定事件已经发生,或者某个条件已经满足。
  3. clear() 方法: 当调用 clear() 方法时,Event 对象的内部标志被重置为 False
  4. wait() 方法: 线程调用 wait() 方法时会被阻塞,直到 Event 对象的内部标志被设置为 True。如果在调用 wait() 时内部标志已经是 True,那么线程将继续执行而不会阻塞。
  5. is_set() 方法: 返回 Event 对象内部标志的当前状态(TrueFalse)。
  • 这里以一个公交车上车的例子讲述
from threading import Thread, Event, Semaphore
import time


def bus(event):
    print('公交车即将到站')
    time.sleep(3)
    print('公交车到站了')
    event.set()


def waiter(i, event):
    print(f'乘客{i}正在等车')
    event.wait()
    print(f'乘客{i}上车出发')


if __name__ == '__main__':
    semaphore = Semaphore(5)
    event = Event()
    t_bus = Thread(target=bus, args=(event,))
    t_waiter_list = [Thread(target=waiter, args=(i, event)) for i in range(1, 11)]
    t_bus.start()
    for t_waiter in t_waiter_list:
        t_waiter.start()

'''
公交车即将到站
乘客1正在等车
乘客2正在等车
乘客3正在等车
乘客4正在等车
乘客5正在等车
乘客6正在等车
乘客7正在等车
乘客8正在等车
乘客9正在等车
乘客10正在等车
公交车到站了
乘客1上车出发
乘客2上车出发
乘客6上车出发
乘客8上车出发
乘客4上车出发
乘客9上车出发
乘客5上车出发
乘客10上车出发
乘客3上车出发
乘客7上车出发

进程已结束,退出代码0

'''
  • 我起了一个bus的线程,还有多个waiter的线程
  • 在bus的公交车到站后面加上一个set()
  • 在乘客上车前加上一个wait()
  • 程序运行到wait()会被阻塞
  • 直到公交车到站,也就是set()将其内部的标志打为True,wait()才不会被阻塞

作者:Esofar

出处:https://www.cnblogs.com/Hqqqq/p/17976973

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   HuangQiaoqi  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示