python 线程锁

1. 什么是线程锁

python官方文档中有两个线程锁的类,一个是class threading.Lock,另一个是class threading.RLock。

1.1 互斥锁(threading.Lock)

threading.Lock也就是原始锁,锁只有两种状态"locked"和"unlocked"。当这种锁在创建后是unlocked。
常用的方法有lock.acquire()和lock.release(),这两个方法是用来改变锁的状态的,前者是unlocked变为locked,后者正相反。
一旦一个线程获得一个锁,会阻塞随后尝试获得锁的其它线程,直到该锁被释放;当然,任何线程都可以释放它。

"""
threading.Lock常用方法介绍
“”“
acquire(blocking=True, timeout=-1)
可以阻塞或非阻塞地获得锁。

当调用时参数 blocking 设置为 True (缺省值),阻塞直到锁被释放,然后将锁锁定并返回 True 。

在参数 blocking 被设置为 False 的情况下调用,将不会发生阻塞。如果调用时 blocking 设为 True 会阻塞,并立即返回 False ;否则,将锁锁定并返回 True。

当浮点型 timeout 参数被设置为正值调用时,只要无法获得锁,将最多阻塞 timeout 设定的秒数。timeout 参数被设置为 -1 时将无限等待。当 blocking 为 False 时,timeout 指定的值将被忽略。

如果成功获得锁,则返回 True,否则返回 False (例如发生 超时 的时候)。

release()
释放一个锁。这个方法可以在任何线程中调用,不单指获得锁的线程。

当锁被锁定,将它重置为未锁定,并返回。如果其他线程正在等待这个锁解锁而被阻塞,只允许其中一个允许。

在未锁定的锁调用时,会引发 RuntimeError 异常。

没有返回值。

简单的例子

import threading

创建一个锁
lock = threading.Lock()
# 上锁,可以指定阻塞[timeout]秒,超过这个时间将不再阻塞其它线程。
lock.acquire([timeout]) 
# 解锁
lock.release()

1.2 递归锁/重入锁(threading.RLock)

重入锁是一个可以被同一个线程多次获取的同步基元组件。在内部,它在基元锁的锁定/非锁定状态上附加了 “所属线程” 和 “递归等级” 的概念。在锁定状态下,某些线程拥有锁 ; 在非锁定状态下, 没有线程拥有它。
class threading.RLock
此类实现了重入锁对象。重入锁必须由获取它的线程释放。一旦线程获得了重入锁,同一个线程再次获取它将不阻塞;线程必须在每次获取它时释放一次。
acquire()/release() 对可以嵌套;只有最终 release() (最外面一对的 release() ) 将锁解开,才能让其他线程继续处理 acquire() 阻塞。

"""
threading.RLock常用方法介绍
“”“
acquire(blocking=True, timeout=-1)
可以阻塞或非阻塞地获得锁。

当无参数调用时: 如果这个线程已经拥有锁,递归级别增加一,并立即返回。否则,如果其他线程拥有该锁,则阻塞至该锁解锁。一旦锁被解锁(不属于任何线程),则抢夺所有权,设置递归等级为一,并返回。如果多个线程被阻塞,等待锁被解锁,一次只有一个线程能抢到锁的所有权。在这种情况下,没有返回值。

当调用时参数 blocking 设置为 True ,和没带参数调用一样做同样的事,然后返回 True 。

当 blocking 参数设置为 False 的情况下调用,不进行阻塞。如果一个无参数的调用已经阻塞,立即返回false;否则,执行和无参数调用一样的操作,并返回True。

当浮点数 timeout 参数被设置为正值调用时,只要无法获得锁,将最多阻塞 timeout 设定的秒数。 如果锁被获取返回 True,如果超时返回False。


release()
释放锁,自减递归等级。如果减到零,则将锁重置为非锁定状态(不被任何线程拥有),并且,如果其他线程正被阻塞着等待锁被解锁,则仅允许其中一个线程继续。如果自减后,递归等级仍然不是零,则锁保持锁定,仍由调用线程拥有。

只有当前线程拥有锁才能调用这个方法。如果锁被释放后调用这个方法,会引起 RuntimeError 异常。

没有返回值。

2. 为什么要使用线程锁

相信大家都知道,在python中多线程是共享全局变量的,多进程是各自都拥有一份属于自己的所有变量。既然多个线程使用同样的一些全局变量,会导致全局变量的不同步。例如:(python版)创建两个线程,其中一个输出1-52,另外一个输出A-Z。输出格式要求:12A 34B 56C 78D 依次类推,该面试题要求使用两个线程,但是要我们控制两个线程的输出是有序的(因为大家都知道,多线程的执行是无序的,咱也不知道输出出来是个什么鬼样子)。所以,当遇到这些情况的时候,需要我们控制线程的访问顺序。
如果不使用线程锁就可能会造成严重的错误。例如,

当我们取钱的时候,你自己去ATM机上取500块钱,同时二弟通过支付宝在同一张银行卡也取500块钱,三弟通过微信也在这张银行卡上取500块钱。如果没有线程锁就可能会出现,银行同时给你们三人各500块钱,然而你的银行卡上只扣了500块钱。

3. 应用场景

I/O密集型操作 需要资源保持同步。因为python的多线程是假的多线程,也就是说,如果我的机器有16个cpu核,但是通过这一个脚本来运行的时候,只会启动一个进程,不管在这个进程中你开了多少了个线程,始终只会使用这16个核中的1个核,因此,造成了在cpu计算密集型时,使用多线程效率低下。

锁的应用场景:

独占锁: 锁适用于访问和修改同一个共享资源的时候,即读写同一个资源的时候。

共享锁: 如果共享资源是不可变的值时,所有线程每一次读取它都是同一样的值,这样的情况就不需要锁。

使用锁的注意事项:

少用锁,必要时用锁。使用了锁,多线程访问被锁的资源时,就变成了串行,要么排队执行,要么争抢执行。
加锁时间越短越好,不需要就立即释放锁。

不使用锁时,有了效率,但是结果是错的。

使用了锁,变成了串行,效率地下,但是结果是对的。

4. 代码实现

4.1 对比阻塞锁和非阻塞锁

阻塞锁和非阻塞锁的区别就在于:
阻塞锁:
当这个锁处于locked状态时,其余线程都会阻塞。此时程序无法执行其余线程的代码

非阻塞锁:
当这个锁处于locked状态时,其余线程不会阻塞。此时其余线程的代码仍然能够执行
什么意思呢?就是说,**多个线程可以同时使用同一个锁,而不会被阻塞!!**例如,使用同一个Lock锁对象时,第二个线程仍可以使用锁,且第一个锁不会被阻塞。

# coding=utf-8

import threading


logic = """
    测试线程锁中的acquire()方法中,采用阻塞和不阻塞的区别
	"""


def blocking(lockk, i):
    try:
        lockk.acquire(blocking=True)
        print(i)
        lockk.release()
    except Exception as e:
        print(e)

def unblocking(lockk, i):
    try:
        lockk.acquire(blocking=False)
        print(i)
        lockk.release()
    except Exception as e:
        print(e)

if __name__ == '__main__':
    lock = threading.Lock()
    
 	for i in range(20):
       t = threading.Thread(target=blocking, args=(lock, i))
       t.start()

    for i in range(20):
       t = threading.Thread(target=unblocking, args=(lock, i))
       t.start()


两次的输出如图所示
在这里插入图片描述
在这里插入图片描述

当时使用阻塞的线程锁时,创建的这20个线程是按照指定的顺序输出。
但是当使用非阻塞的线程锁时,这20个线程的输出并不是按照我们期望的顺序输出,而且其中还出现了异常"release unlocked lock"。意思就是,我们释放了没有上锁的锁。why?
这是因为多个线程同时使用了一个锁,也就是锁线程A上了锁之后,线程B也来上锁,当然这两次调用acquire()方法的返回值是不一样的,因为同一时间只有一个线程能够上锁成功。在释放锁的时候也是,多个线程同时释放一个锁,只有一个能够释放成功,那么这一时刻其它释放该锁的线程都会出现异常!!!

4.2 什么是死锁?

一般来说加锁以后还要有一些功能实现,在释放之前还有可能抛异常,一旦抛出异常,锁是无法释放,但是当前线程可能因为这个异常被终止了,这就产生了死锁。

如何避免死锁

1、使用 try…except…finally 语句处理异常、保证锁的释放

2、with 语句上下文管理,锁对象支持上下文管理。只要实现了__enter__和__exit__魔术方法的对象都支持上下文管理。

只要防止出现异常导致程序终止库可以了

with some_lock:
    # do something...
相当于:

some_lock.acquire()
try:
    # do something...
except Exception as e:
	# do something
finally:
    some_lock.release()

5. 线程锁的缺点

虽然线程锁解决了上面提到了,同步全局变量的问题,但是因为加锁之后,同一时间只能有一个线程操作这个全局变量,这就阻塞了其余的线程,在时间效率上肯定是减慢了不少,这也是么有办法的事情。

6. 参考文献

[1]快速理解线程锁
[2]互联网校招面试必备——Java多线程
[3]python官方文档
[4] [Python 多线程] Lock、阻塞锁、非阻塞锁 (八)

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python中,线程锁是一种用于同步多个线程对共享资源的访问的机制。通过使用线程锁,我们可以确保在某一时刻只有一个线程可以访问共享资源,从而避免多个线程同时对共享资源进行修改而导致的数据竞争问题。Python提供了多种线程锁的实现方式。 其中,threading模块提供了Lock类,可以使用threading.Lock()来创建一个线程锁对象。这个对象可以在需要访问共享资源的代码段前后调用acquire()和release()方法来上锁和解锁。当一个线程上锁后,其他线程需要等待直到锁被释放。 另外,Python还提供了RLock类(可重入锁),即threading.RLock(),它允许同一线程在没有释放其所有权的情况下多次获取同一个锁。这在某些情况下非常有用,比如在递归函数或嵌套调用中需要多次获取锁。 需要注意的是,在Python中,由于全局解释器锁(GIL)的存在,同一时刻只允许一个线程执行Python字节码,因此Python的多线程并不能实现真正的并行计算。如果需要进行并行计算,可以考虑使用multiprocessing模块或其他并行计算框架,如concurrent.futures。 综上所述,Python线程锁是一种用于同步多个线程对共享资源的访问的机制,通过使用Lock类或RLock类可以实现对共享资源的安全访问。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Python:创建线程、多线程的加锁、解锁机制](https://blog.csdn.net/weixin_44775255/article/details/120435611)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [【python多线程中的锁你知道几种?】](https://blog.csdn.net/weixin_41777118/article/details/130416802)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值