python线程锁

304 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

一、线程锁的作用:

锁的核心作用就是为了保证数据的一致性 ,对锁内的资源(变量)进行锁定,避免其它线程偷偷进行篡改 。以达到我们的预期效果。即:异步变同步。

二、线程锁的使用方法:

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()

解释:这样带锁的方式就不会出现数字的跳动。如下图所示:

image.png 如果不使用线程锁,那么线程就会来回的跳动,在线程之间来回变动。不使用线程锁的代码如下:

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()

执行的结果如下图: image.png

四、防止死锁产生

死锁的原因是多种多样的,但是本质就是对资源不合理的竞争锁导致的。 死锁的常见原因:

  • 同一个线程:嵌套获取同一把锁,导致获取释放锁不合理而造成死锁。
  • 多个线程: 不安顺序同时获取多个锁,造成死锁。

第一种就不用说了,这里介绍一下第二种情况:

前提条件:

  • 线程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