python——常见锁类型

1 Lock/RLock 普通锁(互斥锁)

        线程在同一个进程内是共享资源的,很容易发生资源的争抢产生脏数据。互斥锁的作用是解决资源争抢的问题,允许某一部分资源,同时只有一个线程访问。注意:尽量在访问公共资源的时候再使用互斥锁。

        1.1Lock原始锁

         Lock 称为原始锁,获取锁之前不做判断,直到获取锁为止

        1.1.1Lock()实例方法

        1 acquire():尝试获得锁定。使得线程进入同步阻塞状态。

        2 release():释放锁,使用前线程必须已经获得锁定,否则将抛出异常。

        1.1.2小案例

import threading
import time
num=0
# def add_num(i):
#     lock.acquire()
#     global num
#     time.sleep(1)
#     num += i
#     print(f"num is {num}")
#     lock.release()

def add_num(i):
    # 使用with语句管理对象 -- 上下文管理器对象
    # 上下文管理器对象  __enter__,  __exit__
    # __enter__方法做一些初始化工作
    # __exit__方法做一些结束收尾工作  释放资源
    with lock:
        global num
        time.sleep(1)
        num += i
        print(f"num is {num}")


t_list = []

# 生成一个锁实例
lock = threading.Lock()
for i in range(10):
    t = threading.Thread(target=add_num, args=(i,))
    t.start()
[t.join() for t in t_list]

print("ending......")

        1.1.3死锁现象

        如果使用的是Lock(原始锁),在同一个进程,获取原始锁之后,这个锁还没有被释放掉又去尝试获取同一个原始锁,就会产生死锁现象。而使用的是RLock(重入锁),由于它会判断自己是否已经获得了锁,所以它一般不会发生死锁现象。

        1.2RLock重入锁

        RLock 称为重入锁,获取锁之前先判断,如果自己有了锁,就立即返回

        1.2.1 小案例

import threading
lock1 = threading.Lock()
lock2 = threading.RLock()

# 这种情况会产生死锁
# lock1.acquire()
# print("lock1 acqurie 1")
# lock1.acquire()  # 同一个进程,获取原始锁之后,没有释放又去尝试获取同一个 原始锁,就会产生死锁
# print("lock1 acquire 2")
# lock1.release()
# print("lock1 release 1")
# lock1.release()
# print("lock1 release 2")

lock2.acquire()
print("lock2 acqurie 1")
lock2.acquire()
print("lock2 acquire 2")
lock2.release()
print("lock2 release 1")
lock2.release()
print("lock2 release 2")

        这个代码很好地体现出了,Lock与RLock两者之间的异同。


2 信号量(Semaphore)

        信号量锁最多允许同时N个线程执行内容。

        2.1 构造方法

        Semaphore(N): s = threading.Semaphore(N) 创建一个信号量锁对象。N是一个整型参数,表示设置同时允许执行的线程上限。

        2.2 实例方法

        acquire(): 尝试获得锁定,使得线程进入同步阻塞状态。

        release(): 释放锁。使用前线程必须已获得锁定,否则将抛出异常。


3 事件锁

        事件机制:全局定义了一个"Flag",称为标志位。如果标志位的值为False,那么当程序执行wait()就会出现阻塞;如果标志位的值为True,那么wait()方法时便不再阻塞。注意:事件锁不能利用with语句进行使用,只能按照常规方式使用。

        这种锁类似于交通红绿灯(默认是红灯),它属于在红灯的时候一次性阻挡所有线程,在绿灯的时候,一次性放行所有的排队中的线程。

        Event是线程间通信的机制之一:一个线程发送一个event信号,其他线程则等待这个信号。即用主线程控制其他线程的执行

        3.1 构造方法

        Event(): e = threading.Event() 生成一个事件锁对象

        3.2 实例方法

        e.wait([timeout]): 阻塞线程,直到Event对象内部标识位被设置为True或超时,这里可以选择是否设置超时时间的参数。

        set(): 将标志位设置为True

        clear(): 将标志位设置为False

        isSet(): 判断标识位是否为True


4 条件锁 

        该机制会使得线程等待,只有满足某条件时,才会释放n个线程

        4.1 构造方法

        Condition(): c = threading.condition()

        4.2 实例方法

        wait_for(func): 等待函数返回结果,如果结果为True—放行一个线程

        wait()、c.notify(N): 一次性放行N个wait

        acquire(): 上锁         release(): 解锁

        上面的wait和wait_for()需要在上锁和解锁的中间使用


5 以上几种锁的总结

        1 事件锁是基于条件锁来做的,它和条件锁的区别在于一次只能放行全部,不能放行任意个数量的子线程继续运行。

        2 信号量锁也是根据条件锁来做的,它和条件锁和事件锁的区别如下:

             2.1 条件锁一次可以放行任意个处于"等待"状态的线程。

             2.2 事件锁一次可以放行全部的处于"等待"状态的线程。

             2.3 信号量锁,通过规定,成批的放行特定个处于"上锁"状态的线程。 

        3 事件锁内部是基于条件锁来做的

        4 信号量锁内部也是基于条件锁来做的


6 GIL全局解释器锁

        全局解释器锁的英文简称是GIL(Global Interpreter Lock), GIL和Python语言没有任何关系,它只是因为历史原因导致在官方推荐的解释器CPython中遗留的问题,那CPython为什么还被官方推荐呢,是因为CPython的社区十分活跃,而且它拥有的库也十分的丰富。每个线程在执行过程中都需要先获取GIL,保证同一时刻只有一个线程可以执行代码

        GIL最基本的行为只有两个:1.当前执行的线程持有GIL        2.当线程遇到IO阻塞时,会释放GIL。

        由于CIL锁的限制,所以多线程不适合计算型任务,反而更适合IO型任务

        计算密集型任务就是用CPU频繁计算; IO密集型任务就是网络IO(抓取网络数据)、磁盘操作(读写文件)、键盘输入等。

        计算密集型任务——>使用多进程

        IO密集型任务——>使用多线程


猜你喜欢

转载自blog.csdn.net/m0_53891399/article/details/131523276