大家好, 进入第三篇。
今天我们来讲讲,线程里的。
本文目录
何为Lock( 锁 )?
如何使用Lock( 锁 )?
为何要使用锁?
可重入锁(RLock)
防止死锁的加锁机制
饱受争议的GIL(全局锁)
.何为Lock( 锁 )?
何为 ( 锁 ),在网上找了很久,也没有找到合适的定义。可能 这个词已经足够直白了,不需要再解释了。
但是,对于新手来说,我还是要说下我的理解。
我自己想了个生活中例子来看下。
有一个奇葩的房东,他家里有两个房间想要出租给别人,他认为租到自己家房子的人都是一家人,东西都应该是公有的。所以这两个房间的两把锁,用的是同一把钥匙,而且钥匙只有一把。这样A房间的主人只要拿到这把公共的钥匙,也能去B房间。就像在自己家一样。反之亦然。按理说,是没人会租的。但是,有什么样的房东,就能遇到什么样的房客。还真有那么两个人去租他的房子。有一天,X和Y两位房客碰巧同时回到家,两个人工作了一天,都很累,都想第一时间回到自己的小窝去休息。可是钥匙只有一把,两个人得去房东那里去抢,谁先拿到,谁就能先回到家。而另一个人,就得抢到的那个人用完了钥匙才能开门去休息。
回到我们的线程中来,有两个线程A和B,A和B里的程序都加了同一个锁对象,当线程A先执行到(拿到钥匙后),线程B只能等到线程A释放锁后(用完钥匙开门)才能执行程序(开门)。
这个例子,是不是让你清楚了什么是锁呢?
.如何使用Lock( 锁 )?
来简单看下代码,学习如何加锁,获取钥匙,释放锁。
需要注意的是, 和 必须成对出现。否则就有可能造成死循环。
很多时候,我们虽然知道,他们必须成对出现,但是还是难免会有忘记的时候。
为了,规避这个问题。我推荐使用使用来加锁。
语句会在这个代码块执行前自动获取锁,在执行结束后自动释放锁。
.为何要使用锁?
你现在肯定还是一脸懵逼,这么麻烦,我不用锁不行吗?有的时候还真不行。
那么为了说明锁存在的意义。我们分别来看下,不用锁的情形有怎样的问题。
定义两个函数,分别在两个线程中执行。这两个函数 一个变量 。
看代码貌似没什么问题,执行下看看输出
是不是很乱?完全不是我们预想的那样。
解释下这是为什么?因为两个线程共用一个全局变量,又由于两线程是交替执行的,当 执行三次 操作时,就不管三七二十一 给n做了操作。两个线程之间,执行完全没有规矩,没有约束。所以会看到输出当然也很乱。
加了锁后,这个问题也就解决,来看看
由于的线程,率先拿到了钥匙,所以在for循环中,并没有人会拿到这把钥匙对n进行操作。当执行完毕释放锁后,拿到钥匙,才开始自己的for循环。
看看执行结果,真如我们预想的那样。
这里,你应该也知道了,加锁是为了对锁内资源(变量)进行锁定,避免其他线程篡改已被锁定的资源,以达到我们预期的效果。
为了避免大家忘记释放锁,后面的例子,我将都使用上下文管理器来加锁。大家注意一下。
.可重入锁(RLock)
有时候在同一个线程中,我们可能会多次请求同一资源(就是,获取同一锁钥匙),俗称锁嵌套。
如果还是按照常规的做法,会造成死循环的。比如,下面这段代码,你可以试着运行一下。会发现并没有输出结果。
是因为,第二次获取锁钥匙时,发现钥匙已经被同一线程的人拿走了。自己也就理所当然,解不了锁,程序就卡住了。
那么如何解决这个问题呢。
模块除了提供锁之外,还提供了一种可重入锁,专门来处理这个问题。
执行一下,发现已经有输出了。
需要注意的是,可重入锁,只在同一线程里,放松对锁钥匙的获取,其他与并无二致。
.防止死锁的加锁机制
在编写多线程程序时,可能无意中就会写了一个死锁。可以说,死锁的形式有多种多样,但是本质都是相同的,都是对资源不合理竞争的结果。
以本人的经验总结,死锁通常以下几种
同一线程,嵌套获取锁,造成死锁。
多个线程,不按顺序同时获取多个锁。造成死锁
对于第一种,上面已经说过了,使用可重入锁。
主要是第二种。可能你还没明白,是如何死锁的。
举个例子。
线程1,嵌套获取A,B两个锁,线程2,嵌套获取B,A两个锁。
由于两个线程是交替执行的,是有机会遇到线程1获取到锁A,而未获取到锁B,在同一时刻,线程2获取到锁B,而未获取到锁A。由于锁B已经被线程2获取了,所以线程1就卡在了获取锁B处,由于是嵌套锁,线程1未获取并释放B,是不能释放锁A的,这是导致线程2也获取不到锁A,也卡住了。两个线程,各执一锁,各不让步。造成死锁。
经过数学证明,只要两个(或多个)线程获取嵌套锁时,按照固定顺序就能保证程序不会进入死锁状态。
那么问题就转化成如何保证这些锁是按顺序的?
有两个办法
人工自觉,人工识别。
写一个辅助函数来对锁进行排序。
第一种,就不说了。
第二种,可以参考如下代码
如何使用呢?
看到没有,表面上的先获取锁x,再获取锁,而是先获取锁,再获取。
但是实际上,函数,已经对,两个锁进行了排序。所以,都是以同一顺序来获取锁的,是不是造成死锁的。
.饱受争议的GIL(全局锁)
在第一章的时候,我就和大家介绍到,多线程和多进程是不一样的。
多进程是真正的并行,而多线程是伪并行,实际上他只是交替执行。
是什么导致多线程,只能交替执行呢?是一个叫(,全局解释器锁)的东西。
什么是GIL呢?
任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。
需要注意的是,GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。而Python解释器,并不是只有CPython,除它之外,还有,,,等。
在绝大多数情况下,我们通常都认为 Python CPython,所以也就默许了Python具有GIL锁这个事。
都知道GIL影响性能,那么如何避免受到GIL的影响?
使用多进程代替多线程。
更换Python解释器,不使用CPython
好了,关于线程的锁机制,我们大概就介绍这些内容。
关注公众号,获取最新文章
感谢阅读,点个赞再走唄。
领取专属 10元无门槛券
私享最新 技术干货