死锁死锁的原理非常简单,用一句话就可以描述完 。就是当多线程访问多个锁的时候,不同的锁被不同的线程持有,它们都在等待其他线程释放出锁来,于是便陷入了永久等待 。比如A线程持有1号锁,等待2号锁,B线程持有2号锁等待1号锁,那么它们永远也等不到执行的那天,这种情况就叫做死锁 。
关于死锁有一个著名的问题叫做哲学家就餐问题,有5个哲学家围坐在一起,他们每个人需要拿到两个叉子才可以吃饭 。如果他们同时拿起自己左手边的叉子,那么就会永远等待右手边的叉子释放出来 。这样就陷入了永久等待,于是这些哲学家都会饿死 。
文章插图
【Python多线程死锁问题的巧妙解决方法】
这是一个很形象的模型,因为在计算机并发场景当中,一些资源的数量往往是有限的 。很有可能出现多个线程抢占的情况,如果处理不好就会发生大家都获取了一个资源,然后在等待另外的资源的情况 。
对于死锁的问题有多种解决方法,这里我们介绍比较简单的一种,就是对这些锁进行编号 。我们规定当一个线程需要同时持有多个锁的时候,必须要按照序号升序的顺序对这些锁进行访问 。通过上下文管理器我们可以很容易实现这一点 。
上下文管理器首先我们来简单介绍一下上下文管理器,上下文管理器我们其实经常使用,比如我们经常使用的with语句就是一个上下文管理器的经典使用 。当我们通过with语句打开文件的时候,它会自动替我们处理好文件读取之后的关闭以及抛出异常的处理,可以节约我们大量的代码 。
同样我们也可以自己定义一个上下文处理器,其实很简单,我们只需要实现__enter__和__exit__这两个函数即可 。__enter__函数用来实现进入资源之前的操作和处理,那么显然__exit__函数对应的就是使用资源结束之后或者是出现异常的处理逻辑 。有了这两个函数之后,我们就有了自己的上下文处理类了 。
我们来看一个样例:
class Sample:def __enter__(self):print('enter resources')return selfdef __exit__(self, exc_type, exc_val, exc_tb):print('exit')# print(exc_type)# print(exc_val)# print(exc_tb)def doSomething(self):a = 1/1return adef getSample():return Sample()if __name__ == '__main__':with getSample() as sample:print('do something')sample.doSomething()
当我们运行这段代码的时候,屏幕上打印的结果和我们的预期是一致的 。文章插图
我们观察一下__exit__函数,会发现它的参数有4个,后面的三个参数对应的是抛出异常的情况 。type对应异常的类型,val对应异常时的输出值,trace对应异常抛出时的运行堆栈 。这些信息都是我们排查异常的时候经常需要用到的信息,通过这三个字段,我们可以根据我们的需要对可能出现的异常进行自定义的处理 。
实现上下文管理器并不一定要通过类实现,Python当中也提供了上下文管理的注解,通过使用注解我们可以很方便地实现上下文管理 。我们同样也来看一个例子:
import timefrom contextlib import contextmanager@contextmanagerdef timethis(label):start = time.time()try:yieldfinally:end = time.time()print('{}: {}'.format(label, end - start))with timethis('timer'):pass
在这个方法当中yield之前的部分相当于__enter__函数,yield之后的部分相当于__exit__ 。如果出现异常会在try语句当中抛出,那么我们编写except对异常进行处理即可 。避免死锁了解了上下文管理器之后,我们要做的就是在lock的外面包装一层,使得我们在获取和释放锁的时候可以根据我们的需要,对锁进行排序,按照升序的顺序进行持有 。
这段代码源于Python的著名进阶书籍《Python cookbook》,非常经典:
from contextlib import contextmanager# 用来存储local的数据_local = threading.local()@contextmanagerdef acquire(*locks): # 对锁按照id进行排序locks = sorted(locks, key=lambda x: id(x))# 如果已经持有锁当中的序号有比当前更大的,说明策略失败acquired = getattr(_local,'acquired',[])if acquired and max(id(lock) for lock in acquired) >= id(locks[0]):raise RuntimeError('Lock Order Violation')# 获取所有锁acquired.extend(locks)_local.acquired = acquiredtry:for lock in locks:lock.acquire()yieldfinally:# 倒叙释放for lock in reversed(locks):lock.release()del acquired[-len(locks):]
这段代码写得非常漂亮,可读性很高,逻辑我们都应该能看懂,但是有一个小问题是这里用到了threading.local这个组件 。
推荐阅读
- 3种方法实现python-matplotlib显示中文
- 清朝金瓜子价值 古代金瓜子多少钱
- 宝宝胸口有个窝
- 双顶径股骨长标准
- 九华毛峰多少钱斤,黄山毛峰多少钱斤
- 孕38周胎动多胎心正常
- 胎心变化快
- 菊花茶多少度,菊花茶调散
- 加盟祁门红茶多少钱,祁门红茶的价格是多少
- 手上长痱子怎么办?