Python互斥锁(Lock):解决多线程安全问题
多线程的优势在于并发性,即可以同时运行多个任务。但是当线程需要使用共享数据时,也可能会由于数据不同步产生“错误情况”,这是由系统的线程调度具有一定的随机性造成的。
互斥锁的作用就是解决数据不同步问题。关于互斥锁,有一个经典的“银行取钱”问题。银行取钱的基本流程可以分为如下几个步骤:
- 用户输入账户、密码,系统判断用户的账户、密码是否匹配。
- 用户输入取款金额。
- 系统判断账户余额是否大于取款金额。
- 如果余额大于取款金额,则取款成功;如果余额小于取款金额,则取款失败。
乍一看上去,这确实就是日常生活中的取款流程,这个流程没有任何问题。但一旦将这个流程放在多线程并发的场景下,就有可能出现问题。注意,此处说的是有可能,并不是一定。也许你的程序运行了一百万次都没有出现问题,但没有出现问题并不等于没有问题!
按照上面的流程编写取款程序,并使用两个线程分别模拟两个人使用同一个账户做并发取钱操作。此处忽略检查账户和密码的操作,仅仅模拟后面三步操作。下面先定义一个账户类,该账户类封装了账户编号和余额两个成员变量。
class Account: # 定义构造器 def __init__(self, account_no, balance): # 封装账户编号、账户余额的两个成员变量 self.account_no = account_no self.balance = balance
接下来程序会定义一个模拟取钱的函数,该函数根据执行账户、取钱数量进行取钱操作,取钱的逻辑是当账户余额不足时无法提取现金,当余额足够时系统吐出钞票,余额减少。
程序的主程序非常简单,仅仅是创建一个账户,并启动两个线程从该账户中取钱。程序如下:
import threading import time import Account # 定义一个函数来模拟取钱操作 def draw(account, draw_amount): # 账户余额大于取钱数目 if account.balance >= draw_amount: # 吐出钞票 print(threading.current_thread().name\ + "取钱成功!吐出钞票:" + str(draw_amount)) # time.sleep(0.001) # 修改余额 account.balance -= draw_amount print("\t余额为: " + str(account.balance)) else: print(threading.current_thread().name\ + "取钱失败!余额不足!") # 创建一个账户 acct = Account.Account("1234567" , 1000) # 模拟两个线程对同一个账户取钱 threading.Thread(name='甲', target=draw , args=(acct , 800)).start() threading.Thread(name='乙', target=draw , args=(acct , 800)).start()
先不要管程序中第 12 行被注释掉的代码,上面程序是一个非常简单的取钱逻辑,这个取钱逻辑与实际的取钱操作也很相似。
多次运行上面程序,很有可能都会看到如图 1 所示的错误结果。
发表评论