python3线程中的锁机制 您所在的位置:网站首页 python3加锁 python3线程中的锁机制

python3线程中的锁机制

2023-12-27 20:56| 来源: 网络整理| 查看: 265

技术交流QQ群:1027579432,欢迎你的加入! 1.锁的形象解释

有一个奇葩的房东,他家里有两个房间想要出租。这个房东很抠门,家里有两个房间,但却只有一把锁,不想另外花钱是去买另一把锁,也不让租客自己花钱加锁。这样租客只有先租到的那个人才能分配到锁。X先生,率先租到了房子,并且拿到了锁。而后来者Y先生,由于锁已经已经被X取走了,自己拿不到锁,也不能自己加锁,Y就不愿意了,也就不租了。换作其他人也一样,没有人会租第二个房间,直到X先生退租,把锁还给房东,可以让其他房客来取,第二间房间才能租出去。 换句话说,就是房东同时只能出租一个房间,一但有人租了一个房间,拿走了唯一的锁,就没有人再在租另一间房了。 回到线程中来,假设有两个线程A和B,A和B里的程序都加了同一个锁对象,当线程A率先执行到lock.acquire()(拿到全局唯一的锁后),线程B只能等到线程A释放锁lock.release()后(归还锁)才能运行lock.acquire()(拿到全局唯一的锁)并执行后面的代码。

2.如何使用"锁"? import threading # 生成锁对象,全局唯一 lock = threading.Lock() # 获取锁,没有获得到锁的程序会陷入阻塞,直到程序重新获取到锁才能往下执行 lock.acquire() # 释放锁,此时,其他程序可以使用锁了 lock.release()

注意:lock.acquire()与lock.release()必须成对使用,否则会造成死锁!!!为了有时候忘记,推荐使用上下文管理器来加锁,类似于tensorflow中的with tf.Session() as sess:

lock = threading.Lock() with lock: # 写自己的业务逻辑代码 pass

上面的with语句会在代码执行前自动获取锁,在执行结束后自动释放锁

3.可重入锁(RLock)

有时候在同一个线程中,我们可能会多次请求同一资源(就是,获取同一个锁的钥匙),俗称锁嵌套。如果还是按照常规的做法,会造成死锁的。比如,下面这段代码,你可以试着运行一下,会发现并没有输出结果。

import threading def main(): n = 0 lock = threading.Lock() with lock: for i in range(10): n += 1 with lock: print(n) t1 = threading.Thread(target=main) t1.start()

原因:在第二次获取锁时,发现锁已经被同一线程的人拿走了,自己也就理所当然拿不到锁了,所以程序卡住了。 解决方法:threading模块除了提供Lock锁之外,还提供了一种可重入锁RLock,专门来处理这个问题。

import threading def main(): n = 0 lock = threading.RLock() # 生成可重入锁对象 with lock: for i in range(10): n += 1 with lock: print(n) t1 = threading.Thread(target=main) t1.start()

注意: 可重入锁只能用在同一线程里,放松对锁钥匙的获取,其他与普通的Lock没啥不同。

4.防止死锁的加锁机制

死锁出现的情况:1.同一线程中,嵌套获取同一把锁,造成死锁;2.多个线程,不按顺序同时获取多个锁,造成死锁。例如:线程1:嵌套获取A,B两个锁;线程2:嵌套获取B,A两个锁。 由于两个线程是交替执行的,是有机会遇到线程1获取到锁A,而未获取到锁B,在同一时刻,线程2获取到锁B,而未获取到锁A。由于锁B已经被线程2获取了,所以线程1就卡在了获取锁B处,由于是嵌套锁,线程1未获取并释放B,是不能释放锁A的,这是导致线程2也获取不到锁A,也卡住了。两个线程,各执一锁,各不让步。造成死锁。 解决方法: 只要两个(或多个)线程获取嵌套锁时,按照固定顺序就能保证程序不会进入死锁状态。那么问题就转化成如何保证这些锁是按顺序的?(人工自觉,人工识别( 写一个辅助函数来对锁进行排序))

import threading from contextlib import contextmanager # 人工识别方法来排序 # Thread-local state to stored information on locks already acquired _local = threading.local() @contextmanager def acquire(*locks): # Sort locks by object identifier locks = sorted(locks, key=lambda x: id(x)) # Make sure lock order of previously acquired locks is not violated acquired = getattr(_local,'acquired',[]) if acquired and max(id(lock) for lock in acquired) >= id(locks[0]): raise RuntimeError('Lock Order Violation') # Acquire all of the locks acquired.extend(locks) _local.acquired = acquired try: for lock in locks: lock.acquire() yield finally: # Release locks in reverse order of acquisition for lock in reversed(locks): lock.release() del acquired[-len(locks):] # 使用上面定义的人工识别方法 import threading x_lock = threading.Lock() y_lock = threading.Lock() def thread_1(): while True: with acquire(x_lock): with acquire(y_lock): print('Thread-1') def thread_2(): while True: with acquire(y_lock): with acquire(x_lock): print('Thread-2') t1 = threading.Thread(target=thread_1) t1.daemon = True t1.start() t2 = threading.Thread(target=thread_2) t2.daemon = True t2.start()

分析:表面上thread_1的先获取锁x,再获取锁y,而thread_2是先获取锁y,再获取x。 但是实际上,acquire函数,已经对x,y两个锁进行了排序。所以thread_1,hread_2都是以同一顺序来获取锁的,是不会造成死锁的。

5.GIL(全局锁)

多进程是真正的并行,而多线程是伪并行,实际上它只是交替执行。由于GIL导致多线程实际上是伪并行的。因为任何Python线程执行前,必须先获得GIL锁。然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁。所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。 注意:GIL并不是Python的特性,它是在实现Python解释器(CPython)时所引入的一个概念。而Python解释器,并不是只有CPython。除它之外,还有PyPy,Psyco,JPython,IronPython等。在绝大多数情况下,我们通常都认为 Python == CPython,所以也就默许了Python具有GIL锁这个事。 解决方法: 1.使用多进程代替多线程;2.更换Python解释器,不使用CPython

6.参考博客链接

python编程时光 进程与线程的一个简单解释



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有