Python并发编程之谈谈线程中的“锁机制”(三)

大家好, 进入第三篇。

今天我们来讲讲,线程里的。

本文目录

何为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

好了,关于线程的锁机制,我们大概就介绍这些内容。

关注公众号,获取最新文章

感谢阅读,点个赞再走唄。

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180512G0C7FM00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券