
锁通常用来解决程序在使用多线程时因共同访问共享资源所出现的线程安全问题。比如火车站有10个窗口卖火车票,卖完票后则会显示票的余量为0,假设A窗口在卖最后一张票时,另外9个窗口也同时销售最后一张票,那么库存最后会显示为-9,显然不符合正常情况。所以在A窗口卖最后一张票时,从购买到支付完成的这个短暂过程中,其他窗口都不能卖这张票。

银行取款
关于锁的使用,有一个经典的“银行取款”的例子。从银行取钱的流程基本上可以分为如下步骤:
用户输入账户、密码,系统判断用户的账户、密码是否匹配
用户输入取款金额
系统判断账户余额是否大于取款金额
如果余额大于取款金额,则取款成功;如果余额小于取款金额,则取款失败
这就是日常生活中的取款流程。但这个流程如果不使用锁,放在多线程并发的场景下,就有可能出现问题。按照上面的流程编写取款程序,并使用两个线程来模拟两个人同时向一个账户并发取钱。此处忽略检查账户和密码的操作,仅仅模拟后三步操作。
先定义一个账户类,该账户类封装了账户ID和账户余额两个变量。
class Account:def __init__(self, card_id, balance):# 封装账户ID、账户余额的两个变量self.card_id= card_idself.balance = balance
接下来定义一个模拟取钱的函数,该函数根据账户、取款金额进行取钱操作,取钱的逻辑是当账户余额不足时无法提取现金;当余额足够时系统吐出钞票,余额减少。我们创建一个账户,并启动两个线程从该账户中取钱。代码如下:
import threadingimport time# 定义一个函数来模拟取钱操作# 第一个参数为要操作的账户,第二个参数为索取金额def withdraw(account, money):# 账户余额大于取钱数目if account.balance >= money:# 吐出钞票print(threading.current_thread().name + "取钱成功!吐出钞票:" + str(money),end=' ')# time.sleep(0.1)# 修改余额account.balance -= moneyprint("\t余额为: " + str(account.balance))else:print(threading.current_thread().name + "取钱失败!余额不足")# 创建一个账户,银行卡id为8888,存款1000元acct = Account("8888" , 1000)# 模拟两个对同一个账户取钱threading.Thread(name='窗口A', target=withdraw , args=(acct , 800)).start()threading.Thread(name='窗口B', target=withdraw , args=(acct , 800)).start()
我们首先创建一个账户,银行卡id为8888,存款1000元,然后模拟两个窗口同时对8888的银行卡进行取款,取款金额都为800元。正常情况下,只有一个账户可以取款成功,会打印:
窗口A取钱成功! 吐出钞票:800 余额为: 200窗口B取钱失败!余额不足
但是我们多次运行后,会看到如下图所示的错误结果:
窗口A取钱成功!吐出钞票:800窗口B取钱成功!吐出钞票:800余额为: 200余额为: -600
账户余额只有 1000 元,但却取出了 1600 元,账户余额成为了负值,这肯定不是银行所期望的结果。原因就是线程调度的不确定性,使得多线程在同一时段对都访问了临界资源,就是账户余额。如果我们把第10行代码time.sleep(0.1)取消注释,那么在一个线程sleep时,系统就会切换到另一个窗口对账户进行取款操作,这时第一个线程只判断了取款金额是否小于存款金额,还没对存款金额进行扣除,第二个线程就对账户进行了访问,它们同时访问临界资源,出现如上错误。
所以我们需要使得取款这一过程,在一段时间内只能被一个线程所访问,当这个线程访问结束后,另一线程才可继续访问。
同步锁
为了解决这个问题,Python 的 threading 模块引入了锁。threading 模块提供了 Lock 类,它有如下方法加锁和释放锁:
acquire(blocking=True, timeout=-1):对 Lock加锁,其中timeout参数指定加锁多少秒
release():释放锁
class Account:def __init__(self, card_id, balance):# 封装账户ID、账户余额的两个变量self.card_id= card_idself.balance = balancedef withdraw(account, money):# 进行加锁lock.acquire()# 账户余额大于取钱数目if account.balance >= money:# 吐出钞票print(threading.current_thread().name + "取钱成功!吐出钞票:" + str(money),end=' ')# 修改余额account.balance -= moneyprint("\t余额为: " + str(account.balance))else:print(threading.current_thread().name + "取钱失败!余额不足")# 进行解锁lock.release()# 创建一个账户,银行卡id为8888,存款1000元acct = Account("8888" , 1000)# 模拟两个对同一个账户取钱# 在主线程中创建一把锁lock = threading.Lock()threading.Thread(name='窗口A', target=withdraw , args=(acct , 800)).start()threading.Thread(name='窗口B', target=withdraw , args=(acct , 800)).start()
点击【阅读全文】直达B站



