本文已参与「新人创作礼」活动,一起开启掘金创作之路。
一、线程锁的作用:
锁的核心作用就是为了保证数据的一致性 ,对锁内的资源(变量)进行锁定,避免其它线程偷偷进行篡改 。以达到我们的预期效果。即:异步变同步。
二、线程锁的使用方法:
1、直接使用:
import threading
#创建锁对象
lock = threading.Lock()
#获取锁
lock.acquire()
#释放锁
lock.release()
解释:acquire()和release() 是成对出现的。往往死锁的出现就是release没有执行。
2、with关键字:
threading.Loc() : 创建锁对象
with : 上下文管理器来获取,释放锁
解释:with是可以自动获取锁和释放锁的,可以防止我们忘记释放锁而造成死锁的情况发生。
三、使用线程锁的原因:
由于多线程同时在完成特定的操作时,所以在完成操作的过程中可能会被打断,去做其他的操作,可能会产生脏数据。 例如:一个线程读取变量n,[初始值为0],然后n++,最后输出n。当访问n++后,被打断,有另外的线程做同样的工作。这时n被加了2次,所以n最后等于2,而不是1。
举例说明:
from time import sleep, ctime
from threading import Thread, Lock, currentThread
from atexit import register
lock = Lock()
print(type(lock))
def fun():
lock.acquire() # 加锁
for i in range(4):
print("Thread Name","=",currentThread().name,"i","=",i)
sleep(random.randint(1,5))
lock.release() # 解锁
def main():
for i in range(3):
Thread(target=fun).start()
@register
def exit():
print("线程执行完毕:", ctime())
main()
解释:这样带锁的方式就不会出现数字的跳动。如下图所示:
如果不使用线程锁,那么线程就会来回的跳动,在线程之间来回变动。不使用线程锁的代码如下:
import random
from time import sleep, ctime
from threading import Thread, Lock, currentThread
from atexit import register
lock = Lock()
print(type(lock))
def fun():
# lock.acquire() # 加锁
for i in range(4):
print("Thread Name","=",currentThread().name,"i","=",i)
sleep(random.randint(1,5))
# lock.release() # 解锁
def main():
for i in range(3):
Thread(target=fun).start()
@register
def exit():
print("线程执行完毕:", ctime())
main()
执行的结果如下图:
四、防止死锁产生
死锁的原因是多种多样的,但是本质就是对资源不合理的竞争锁导致的。 死锁的常见原因:
- 同一个线程:嵌套获取同一把锁,导致获取释放锁不合理而造成死锁。
- 多个线程: 不安顺序同时获取多个锁,造成死锁。
第一种就不用说了,这里介绍一下第二种情况:
前提条件:
- 线程1:嵌套获取A,B两个锁
- 线程2: 嵌套获取B,A两个锁 执行情况:
- 线程1获取了A锁,而没有获取B锁
- 线程2获取了B锁,而没有获取A锁 执行结果:
- 线程1在等待线程2释放B锁,然后结束任务,
- 线程2在等待线程1释放A锁,然后结束任务,
- 最终两个线程就陷入了相互等待的局面了。这个就是死锁!
全局锁
我们都知道多进程是真正的并行执行,多线程只是交替执行。
python中导致线程交替执行的是一个叫GIL【Global Interpreter Lock】全局锁的东西。
什么GIL?
Python在线程执行前,必须加上一个GIL锁,然后,每执行100条字节码,解释器就释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都上锁了,所以,多线程在Python 中只能交替执行,即使是100个线程跑在了100核的CPU上,也只有一个核在运算。
注意:GIL不是Python本身的特性,而是它的解析器之一的CPython的特性。Python的解析器还有:PyPy,Psyco,JPython等。只是我们绝大多数情况下,都是使用的是CPython这个解析器,所以也就默认了 Python有GIL 这个特性了。
都知道GIL 影响性能, 那么如何避免受到GIL的影响?
- 使用多进程代替多线程
- 更换Python解析器,不使用CPython