一、为什么不安全
【1】假设a的银行账户有balance=500,此时公司像银行账户转入工资1000元,a此时又在淘宝上购买200元的商品。因为并发,如果此时银行获取balance=500,在银行系统内a的账户内就有1500元,此时该结果还没返回到a账户,这个时候淘宝也拿到了balance=500,并且减-200=300,返回到a账户内,此时balance=300;淘宝结算结束后,银行系统内的结果才返回到a账户,这个时候a账户内balance=1500
【2】不进行锁 ,则以下代码会造成输出的时候,有时候是200,有时候是1500
# -*- coding:utf-8 -*- # __author__:pansy # 2022/5/11 # 账户余额 import time,threading account_balance = 500 def foo(num): # 需要返回最终的account_balance,因为该变量是全局变量,在局部作用域内只能声明,不能修改;如果要修改必须声明为global global account_balance # 引用账户余额,赋值给balance(引用的时候不需要将account_balance声明为global) balance = account_balance time.sleep(1) # balance变量要加上函数传进来的入参 balance = balance + num # 返回最终的结果,此时修改了account_balance,所以要 global account_balance account_balance = balance # 创建线程 t1 = threading.Thread(target=foo,args=(1000,)) # 银行打钱进来 t2 = threading.Thread(target=foo,args=(-300,)) # 淘宝消费 # 启动线程 t1.start() t2.start() # 需要阻塞主线程,当t1和t2线程结束后,主线程再执行打印,然后再结束主线程 t1.join() t2.join() # 最终余额打印 print('最终余额为:%.2f' % account_balance) # 输出可能是200.00,可能是1500.00
二、同步锁
【1】既然不安全,那只需要把重要数据balance锁住,当线程1在使用balance的时候,把balance锁定只能给该线程用;线程2被挡住无法使用balance;当线程1使用完balance后,再放开balance,线程2调用balance同时再次将balance锁住
【2】创建对象threading.Lock()
# -*- coding:utf-8 -*- # __author__:pansy # 2022/5/11 # 账户余额 import time,threading # 创建锁 r = threading.Lock() # 同步锁 account_balance = 500 def foo(num): # 在操作重要数据前,先锁定 r.acquire() # 需要返回最终的account_balance,因为该变量是全局变量,在局部作用域内只能声明,不能修改;如果要修改必须声明为global global account_balance # 引用账户余额,赋值给balance(引用的时候不需要将account_balance声明为global) balance = account_balance time.sleep(1) # balance变量要加上函数传进来的入参 balance = balance + num # 返回最终的结果,此时修改了account_balance,所以要 global account_balance account_balance = balance # 操作完重要数据后,解锁 r.release() # 创建线程 t1 = threading.Thread(target=foo,args=(1000,)) # 银行打钱进来 t2 = threading.Thread(target=foo,args=(-300,)) # 淘宝消费 # 启动线程 t1.start() t2.start() # 需要阻塞主线程,当t1和t2线程结束后,主线程再执行打印,然后再结束主线程 t1.join() t2.join() # 最终余额打印 print('最终余额为:%.2f' % account_balance) # 最终输出始终为1200.00