前言
在多线程场景中,除了同步线程操作,还有一点很重要,那就是要能够控制对共享资源的访问,从而避免破坏或丢失数据。Python的内置数据结构(列表、字典等)是线程安全的, 这是Python使用原子字节码来管理这些数据结构的一个副作用(更新数据结构过程中不会释放GIL)。而Python中其他数据结构或更简单的类型则没有这个保护。要安全的访问一个对象,可以使用Lock对象。
假设这样一个场景:两个人同时在一个银行账户取钱,余额不足是取不出来的,如果不对账户余额(共享资源)进行访问控制,会出现什么情况呢?看下边的代码示例
代码展示
没有对余额进行访问控制的情况
import time
import logging
from threading import Thread, Barrier, Lock
logging.basicConfig(level=logging.DEBUG, format='%(threadName)s-%(asctime)s-%(message)s')
class Account:
def __init__(self):
self.balance = 100 # 余额
def get_money(self, v):
if self.balance >= v:
logging.debug('正在出钞:%d 元,请稍后...', v)
time.sleep(1)
self.balance -= v
logging.debug('余额:%d 元', self.balance)
else:
logging.debug('余额不足:%d 元', self.balance)
def get_money(b: Account, v: int):
b.get_money(v)
if __name__ == '__main__':
b = Account()
t1 = Thread(target=get_money, args=(b, 50))
t2 = Thread(target=get_money, args=(b, 60))
t1.start()
t2.start()
余额总共100元,线程1取50, 线程2取60, 结果:
Thread-1-2022-06-27 11:10:34,009-正在出钞:50 元,请稍后...
Thread-2-2022-06-27 11:10:34,010-正在出钞:60 元,请稍后...
Thread-2-2022-06-27 11:10:35,024-余额:40 元
Thread-1-2022-06-27 11:10:35,024-余额:-10 元
很明显,这并不是我们期望看到的结果。我们需要对余额进行访问控制,保证同一时刻只有一个线程对余额进行修改
对余额进行访问控制的情况
def get_money(self, v):
self.lock.acquire()
if self.balance >= v:
logging.debug('正在出钞:%d 元,请稍后...', v)
time.sleep(1)
self.balance -= v
logging.debug('余额:%d 元', self.balance)
else:
logging.debug('余额不足:%d 元', self.balance)
self.lock.release()
结果如下,符合我们的预期
Thread-1-2022-06-27 11:29:14,400-正在出钞:50 元,请稍后...
Thread-1-2022-06-27 11:29:15,403-余额:50 元
Thread-2-2022-06-27 11:29:15,403-余额不足:账户只有 50 元