線程同步技術:
解決多個線程爭搶同一個資源的情況,線程協作工作。一份數據同一時刻只能有一個線程處理。
解決線程同步的幾種方法:
Lock、RLock、Condition、Barrier、semaphore
1)Lock 鎖
鎖,一旦線程獲得鎖,其它試圖獲取鎖的線程將被阻塞。
當用阻塞參數設置為 False 時, 不要阻止。如果將阻塞設置為 True 的調用將阻止, 則立即返回 False;否則, 將鎖定設置為鎖定並返回 True。
Lock的方法:
acquire(blocking=True,timeout=-1) 加鎖。默認True阻塞,阻塞可以設置超時時間。非阻塞時成功獲取鎖返回True,否則返回False。
當blocking設置為False時,不阻塞,同一個鎖對象,其它線程可以重用,但最后都必須釋放。
如果設置為True(默認True),其它試圖調用鎖的線程將阻塞,並立即返回False。阻塞可以設置超時時間。
release() 釋放鎖。可以從任何線程調用釋放。已上鎖的鎖,會被重置為unlocked,對未上鎖的鎖調用,會拋RuntimeError異常: cannot release un-acquired lock。
不使用Lock的例子:
#不使用Lock鎖的例子 import logging import threading,time logging.basicConfig(level=logging.INFO) # 10 -> 100cups cups = [] lock = threading.Lock() def worker(lock:threading.Lock,task=100): while True: count = len(cups) time.sleep(0.1) if count >= task: break logging.info(count) cups.append(1) logging.info("{} make 1........ ".format(threading.current_thread().name)) logging.info("{} ending=======".format(len(cups))) for x in range(10): threading.Thread(target=worker,args=(lock,100)).start() 運行結果: INFO:root:Thread-7 make 1........ INFO:root:93 INFO:root:Thread-5 make 1........ INFO:root:95 INFO:root:Thread-6 make 1........ INFO:root:92 INFO:root:Thread-2 make 1........ INFO:root:94 INFO:root:Thread-8 make 1........ INFO:root:97 INFO:root:Thread-10 make 1........ INFO:root:96 INFO:root:Thread-4 make 1........ INFO:root:98 INFO:root:Thread-1 make 1........ INFO:root:99 INFO:root:Thread-9 make 1........ INFO:root:109 ending======= INFO:root:109 ending======= INFO:root:109 ending=======
還是使用前面的10個工人生產100杯子的例子, 當做到99個杯子時,10個工人都發現還少一個,都去做了一個,一共做了109個,超出了100個,就發生了不可預期的結果。
臨界線判斷失誤,多生產了杯子。
解決方法就可以用鎖,來解決資源爭搶。當一個人看杯子數量時,就上鎖,其它人只能等着,看完杯子后發現少一個就把這最后一個做出來,然后數量加一,解鎖,其他人再看到已經有100個杯子時,就可以停止工作。
加鎖的時機非常重要:看杯子數量時加鎖,增加數量后釋放鎖。
使用Lock的例子:
#Lock import logging import threading import time logging.basicConfig(level=logging.INFO) # 10 -> 100cups cups = [] lock = threading.Lock() def worker(lock:threading.Lock,task=100): while True: if lock.acquire(False): count = len(cups) time.sleep(0.1) if count >= task: lock.release() break logging.info(count) cups.append(1) lock.release() logging.info("{} make 1........ ".format(threading.current_thread().name)) logging.info("{} ending=======".format(len(cups))) for x in range(10): threading.Thread(target=worker,args=(lock,100)).start() 運行結果: INFO:root:0 INFO:root:Thread-1 make 1........ INFO:root:1 INFO:root:Thread-5 make 1........ INFO:root:2 INFO:root:Thread-6 make 1........ .... INFO:root:Thread-3 make 1........ INFO:root:97 INFO:root:Thread-3 make 1........ INFO:root:98 INFO:root:Thread-4 make 1........ INFO:root:99 INFO:root:Thread-3 make 1........ INFO:root:100 ending======= INFO:root:100 ending======= INFO:root:100 ending======= .....
在使用了鎖以后,雖然保證了結果的准確性,但是性能下降了很多。
一般來說加鎖以后還要有一些功能實現,在釋放之前還有可能拋異常,一旦拋出異常,鎖是無法釋放,但是當前線程可能因為這個異常被終止了,這就產生了死鎖。
死鎖解決辦法:
1、使用 try..except..finally 語句處理異常、保證鎖的釋放
2、with 語句上下文管理,鎖對象支持上下文管理。只要實現了__enter__和__exit__魔術方法的對象都支持上下文管理。
鎖的應用場景:
獨占鎖: 鎖適用於訪問和修改同一個共享資源的時候,即讀寫同一個資源的時候。
共享鎖: 如果共享資源是不可變的值時,所有線程每一次讀取它都是同一樣的值,這樣的情況就不需要鎖。
使用鎖的注意事項:
- 少用鎖,必要時用鎖。使用了鎖,多線程訪問被鎖的資源時,就變成了串行,要么排隊執行,要么爭搶執行。
- 加鎖時間越短越好,不需要就立即釋放鎖。
- 一定要避免死鎖。
不使用鎖時,有了效率,但是結果是錯的。
使用了鎖,變成了串行,效率地下,但是結果是對的。
import threading import time lock = threading.Lock() def work(): print('working..') time.sleep(0.2) lock.release() # 1解鎖 lock.acquire() # 1上鎖 print("get locker 1") threading.Thread(target=work).start() time.sleep(1) lock.acquire() # 2上鎖 print("get locker 2") threading.Thread(target=work).start() print("release locker") 運行結果: get locker 1 working.. get locker 2 working.. release locker
同一個鎖對象在釋放后可以再次使用。
但是如果同一把鎖加鎖后,又被別人拿了,自己就阻塞了:
import threading import time lock = threading.Lock() def work(): print('working..') time.sleep(0.2) lock.release() # 1解鎖 lock.acquire() # 1上鎖 print("get locker 1") lock.acquire() # 2上鎖 print("get locker 2") threading.Thread(target=work).start() threading.Thread(target=work).start() print("release locker") 運行結果: get locker 1 阻塞狀態....
阻塞鎖:
#阻塞鎖 import threading,time lock = threading.Lock() def foo(): ret = lock.acquire() print("{} Locked. {}".format(ret,threading.current_thread())) time.sleep(10) threading.Thread(target=foo).start() threading.Thread(target=foo).start() 運行結果: True Locked. <Thread(Thread-1, started 123145559191552)>
lock.acquire()默認設置blocking=True,兩個線程使用同一個Lock鎖對象,只要Thread-1線程不釋放,第二個線程就無法獲取鎖,且會使Thread-1線程阻塞。
如果想讓多個線程同時都可以使用一個鎖對象,就必須使用非阻塞鎖,或者第一個線程使用完鎖之后立刻釋放,然后第二個線程再使用。
非阻塞鎖:
#非阻塞鎖 import threading,time lock = threading.Lock() def foo(): ret = lock.acquire(False) print("{} Locked. {}".format(ret,threading.current_thread())) time.sleep(10) threading.Thread(target=foo).start() threading.Thread(target=foo).start() 運行結果: True Locked. <Thread(Thread-1, started 123145516146688)> False Locked. <Thread(Thread-2, started 123145521401856)> Process finished with exit code 0
lock.acquire(False)設置blocking=False表示不阻塞,使用同一個Lock鎖對象時,第二個線程仍可以使用鎖,且第一個鎖不會被阻塞。
非阻塞鎖2:
#非阻塞鎖 import threading,logging,time FORMAT = '%(asctime)s\t [%(threadName)s,%(thread)d] %(message)s' logging.basicConfig(level=logging.INFO,format=FORMAT) def worker(tasks): for task in tasks: time.sleep(0.01) if task.lock.acquire(False): #False非阻塞 logging.info('{} {} begin to start'.format(threading.current_thread().name,task.name)) else: logging.info('{} {} is working'.format(threading.current_thread().name,task.name)) class Task: def __init__(self,name): self.name = name self.lock = threading.Lock() tasks = [Task('task={}'.format(t)) for t in range(5)] for i in range(3): t = threading.Thread(target=worker,name='worker-{}'.format(i),args=(tasks,)) t.start() 運行結果: 2017-12-19 16:37:49,556 [worker-2,123145390018560] worker-2 task=0 begin to start 2017-12-19 16:37:49,556 [worker-1,123145384763392] worker-1 task=0 is working 2017-12-19 16:37:49,557 [worker-0,123145379508224] worker-0 task=0 is working 2017-12-19 16:37:49,567 [worker-2,123145390018560] worker-2 task=1 begin to start 2017-12-19 16:37:49,567 [worker-1,123145384763392] worker-1 task=1 is working 2017-12-19 16:37:49,568 [worker-0,123145379508224] worker-0 task=1 is working 2017-12-19 16:37:49,580 [worker-1,123145384763392] worker-1 task=2 begin to start 2017-12-19 16:37:49,580 [worker-2,123145390018560] worker-2 task=2 is working 2017-12-19 16:37:49,580 [worker-0,123145379508224] worker-0 task=2 is working 2017-12-19 16:37:49,591 [worker-1,123145384763392] worker-1 task=3 begin to start 2017-12-19 16:37:49,592 [worker-2,123145390018560] worker-2 task=3 is working 2017-12-19 16:37:49,592 [worker-0,123145379508224] worker-0 task=3 is working 2017-12-19 16:37:49,604 [worker-1,123145384763392] worker-1 task=4 begin to start 2017-12-19 16:37:49,604 [worker-2,123145390018560] worker-2 task=4 is working 2017-12-19 16:37:49,604 [worker-0,123145379508224] worker-0 task=4 is working