在多線程場景中,除了同步線程操作,還有一點很重要,那就是要能夠控制對共享資源的訪問,從而避免破壞或丟失數據。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 元