锁
什么是锁?用来干什么的?
我们先拿生活中的例子来说。这里用多进程说明:
购票大家都不陌生,我们购票就好比一个系统开了多进程让我们多个用户去买票,我们可以同时查询到票的数量。这时候是春节期间购票高峰期,有些票只剩下寥寥几张,而需要买这张票的有成百上千人,我们知道进程之间数据是隔离的,因为隔离我们无法及时返回购票信息使数量减少,可能导致有几十人都买到这个票了,但实际票只有3张,这怎么呢?先看代码示例:
这里我们用文件来做数据存储,我定义一个count文件,里面假设存了3张票:
{"count": 3}
python程序文件,这里我们假设有10个人,需要抢这3张票,你运行下面代码看看有几个人买到了票?:
import json from multiprocessing import Process def search_count(user): with open('count') as f: dic = json.load(f) print('%s 查询到余票有 %s 张'%(user,dic['count'])) def buy_count(user): with open('count') as f: dic = json.load(f) if dic['count'] > 0: dic['count'] -= 1 print('%s 买到票了'%user) else: print('%s 没有买到票'%user) with open('count','w') as f: json.dump(dic,f) def run(user): search_count(user) buy_count(user) if __name__ == '__main__': for i in range(10): p = Process(target=run,args=('user %s'%i,)) p.start()
这时候你发现有好多人都买到了票,但这是错误的,因为只有三张票吗,买到这么多还不得抢座乱套啊,所以为了社会的和平稳定,我——来自银河......(跑题了)。
我们把这个票比作一个仓库,既然你们这么多人都要来仓库拿东西,我就给你加上一把锁,只有一个钥匙,钥匙在锁上挂着,谁先来谁把钥匙拿走进去后把门反锁自己一个人取东西,其他人只能等他拿过后再取了。这就是加锁的作用。来看代码:
#!/usr/bin/evn python # -*- coding:utf-8 -*- import json from multiprocessing import Process, Lock # Lock导入锁 def search_count(user): with open('count') as f: dic = json.load(f) print('%s 查询到余票有 %s 张' % (user, dic['count'])) def buy_count(user, lock): lock.acquire() # 为下面代码加锁,只能一个线程运行 with open('count') as f: dic = json.load(f) if dic['count'] > 0: dic['count'] -= 1 print('%s 买到票了' % user) else: print('%s 没有买到票' % user) with open('count', 'w') as f: json.dump(dic, f) lock.release() # 释放锁,让后面的程序运行 def run(user, lock): search_count(user) buy_count(user, lock) if __name__ == '__main__': lock = Lock() # 创建锁 for i in range(10): p = Process(target=run, args=('user %s' % i, lock)) p.start()
这时候你就发现有几张票就只能几个人买票了,再也不会为了一张票几个人打架啦。
注意:运行中可能会报错,是因为多个进程同时访问一个文件导致的,但是并不会对程序造成影响!
还可以使用with打开文件的形式去使用锁,就可以不用再写加锁和释放锁了,并且with有异常处理机制,不会因为加锁后程序Over到仓库里导致钥匙无法归还使后面的程序无法运行,从而造成死锁。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#!/usr/bin/evn python # -*- coding:utf-8 -*- import json from multiprocessing import Process, Lock # Lock导入锁 def search_count(user): with open('count') as f: dic = json.load(f) print('%s 查询到余票有 %s 张' % (user, dic['count'])) def buy_count(user, lock): with lock: # with 自动处理加锁、释放 # lock.acquire() # 为下面代码加锁,只能一个线程运行 with open('count') as f: dic = json.load(f) if dic['count'] > 0: dic['count'] -= 1 print('%s 买到票了' % user) else: print('%s 没有买到票' % user) with open('count', 'w') as f: json.dump(dic, f) # lock.release() # 释放锁,让后面的程序运行 def run(user, lock): search_count(user) # with lock: # 也可以写在这里 buy_count(user, lock) if __name__ == '__main__': lock = Lock() # 创建锁 for i in range(10): p = Process(target=run, args=('user %s' % i, lock)) p.start()
总结:
加锁不仅在进程中使用,在线程中同样适用!
# 1.如果在一个并发的场景下,涉及到某部分内容 # 是需要修改一些所有进程共享数据资源 # 需要加锁来维护数据的安全 # 2.在数据安全的基础上,才考虑效率问题 # 3.同步存在的意义 # 数据的安全性 # 在主进程中实例化 lock = Lock() # 把这把锁传递给子进程 # 在子进程中 对需要加锁的代码 进行 with lock: # with lock相当于lock.acquire()和lock.release() # 在进程中需要加锁的场景 # 共享的数据资源(文件、数据库) # 对资源进行修改、删除操作 # 加锁之后能够保证数据的安全性 但是也降低了程序的执行效率
补充:
递归锁
先来看一个死锁问题,什么是死锁呢?同一个线程有多把锁,需要完成一个线程就需要拿到这个线程的所有钥匙,不完成不罢休。此时多个线程分别拿着不同的钥匙无法完成自己的任务退出就是死锁现象。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#!/usr/bin/env python # -*- coding:utf-8 -*- import time from threading import Thread,Lock noodle_lock = Lock() fork_lock = Lock() def eat1(name,noodle_lock,fork_lock): noodle_lock.acquire() print('%s抢到面了'%name) fork_lock.acquire() print('%s抢到叉子了' % name) print('%s吃了一口面'%name) time.sleep(0.1) fork_lock.release() print('%s放下叉子了' % name) noodle_lock.release() print('%s放下面了' % name) def eat2(name,noodle_lock,fork_lock): fork_lock.acquire() print('%s抢到叉子了' % name) noodle_lock.acquire() print('%s抢到面了'%name) print('%s吃了一口面'%name) time.sleep(0.1) noodle_lock.release() print('%s放下面了' % name) fork_lock.release() print('%s放下叉子了' % name) lst = ['alex','wusir','taibai','yuan'] Thread(target=eat1,args=(lst[0],noodle_lock,fork_lock)).start() Thread(target=eat2,args=(lst[1],noodle_lock,fork_lock)).start() Thread(target=eat1,args=(lst[2],noodle_lock,fork_lock)).start() Thread(target=eat2,args=(lst[3],noodle_lock,fork_lock)).start()
# 锁 # 互斥锁 # 在一个线程中连续多次acquire会死锁 # 递归锁 # 在一个线程中连续多次acquire不会死锁 # 死锁现象 # 死锁现象是怎么发生的? # 1.有多把锁,一把以上 # 2.多把锁交替使用 # 怎么解决(*****) # 递归锁 —— 将多把互斥锁变成了一把递归锁 # 快速解决问题 # 效率差 # ***递归锁也会发生死锁现象,多把锁交替使用的时候 # 优化代码逻辑 # 可以使用互斥锁 解决问题 # 效率相对好 # 解决问题的效率相对低
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#!/usr/bin/env python # -*- coding:utf-8 -*- from threading import RLock ''' # rlock = RLock() # rlock.acquire() # print('*'*20) # rlock.acquire() # print('-'*20) # rlock.acquire() # print('*'*20) # 在同一个线程中,可以连续acuqire多次不会被锁住 # 递归锁 好不好?: # 好 :在同一个进程中多次acquire也不会发生阻塞 # 不好 :占用了更多资源 ''' # 递归锁解决死锁就是把锁替换成递归锁,并把锁(锁的变量)合并为一把 import time from threading import RLock,Thread # noodle_lock = RLock() # 这样使用递归锁不正确,和普通锁区别不大 # fork_lock = RLock() noodle_lock = fork_lock = RLock() # 把两个锁的钥匙合并为一把万能钥匙,才是 递归锁 快速解决死锁现象的方法 print(noodle_lock,fork_lock) def eat1(name,noodle_lock,fork_lock): noodle_lock.acquire() print('%s抢到面了'%name) fork_lock.acquire() print('%s抢到叉子了' % name) print('%s吃了一口面'%name) time.sleep(0.1) fork_lock.release() print('%s放下叉子了' % name) noodle_lock.release() print('%s放下面了' % name) def eat2(name,noodle_lock,fork_lock): fork_lock.acquire() print('%s抢到叉子了' % name) noodle_lock.acquire() print('%s抢到面了'%name) print('%s吃了一口面'%name) time.sleep(0.1) noodle_lock.release() print('%s放下面了' % name) fork_lock.release() print('%s放下叉子了' % name) lst = ['alex','wusir','taibai','yuan'] Thread(target=eat1,args=(lst[0],noodle_lock,fork_lock)).start() Thread(target=eat2,args=(lst[1],noodle_lock,fork_lock)).start() Thread(target=eat1,args=(lst[2],noodle_lock,fork_lock)).start() Thread(target=eat2,args=(lst[3],noodle_lock,fork_lock)).start()