阿宁的Python学习-----锁

锁(lock)

锁的使用姿势

姿势一
  • threading.lock(): 创建锁对象
  • acquire(): 获取锁
  • release (): 释放锁
import threading
 
#创建锁对象
lock = threading.Lock()
 
#获取锁
lock.acquire()
 
#释放锁
lock.release()

注释: acquire() 和 release() 是成对出现的。往往死锁的出现就是 release 没有执行

姿势二
  • threading.lock(): 创建锁对象
  • with: 上下文管理器来获取,释放锁
import  threading
  
lock = threading.Lock()
  
with lock:
    #业务代码
    pass

注释: with 是可以 自动 获取锁 和 释放锁的,可以防止我们忘记释放锁而造成死锁的情况发生

锁的作用

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

import threading
def work1():
    global n
    for i in range(3):
        n += 1
        print('work...',n)
def work2():
    global n
    for i in range(3):
        n += 5
        print('work2 ...',n)
n = 0
t1 = threading.Thread(target=work1)
t2 = threading.Thread(target=work2)
t1.start()
t2.start()

#输出结果
work... 1
work... 2
work... 3
work2 ... 8
work2 ... 13
work2 ... 18

注释:由于线程是内核级别的,它的切换时由CPU说了算,两线程间的执行顺序是完全没有约束的,轮到谁了就谁上。所以整个结果是无序的。

import threading
def work1():
    global n , lock
    lock.acquire()
    for i in range(5):
        n += 1
        print('work1 ......',n)
    lock.release()
def work2():
    global n, lock
    with lock:
        for i in range(5):
            n += 5
            print('work2******',n)
n = 0
lock = threading.Lock()
t1 = threading.Thread(target=work1)
t2 = threading.Thread(target=work2)
t1.start()
t2.start()

#输出结果
work1 ...... 1
work1 ...... 2
work1 ...... 3
work1 ...... 4
work1 ...... 5
work2****** 10
work2****** 15
work2****** 20
work2****** 25
work2****** 30

解析:

  • work1 由于先启动,它先锁定了资源
  • work2 切换进来的时候,发现资源是锁定的,所以它只能继续等待
  • work2 会在 “切换”与“等待”这两个状态间进行轮替,直到work1 释放锁
防止死锁产生

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

死锁的常见原因

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

多个线程:

前提条件

  • 线程1 : 嵌套获取 A , B 两个锁
  • 线程2 : 嵌套获取 B , A 两个锁

执行情况

  • 线程1 获取了A 锁 , 而没有获取B 锁
  • 线程2 获取了B 锁 , 而没有获取A 锁

执行结果

  • 线程1 在等待线程2 释放B 锁 然后结束任务,
  • 线程2 在等待 线程1 释放A 锁, 然后结束任务
  • 最终两个线程就陷入了相互等待的局面了。这个就是死锁
from threading import Thread,Lock
x_lock = Lock()
y_lock = Lock()

def job1():
    for i in range(5):
        with x_lock:
            with y_lock:
                print('this is job1','*'*5)
def job2():
    for i in range(5):
        with x_lock:
            with y_lock:
                print('this is job2','*'*10)
t1 = Thread(target=job1)
t2 = Thread(target=job2)
t1.start()
t2.start()
全局锁

我们都知道 多进程是真正的并行执行 , 多线程只是交替执行。

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

另外, Python 的线程在I/O 开销比较大的情况下,优势还是特别明显的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值