[Python 多線程] Lock、阻塞鎖、非阻塞鎖 (八)


 

 線程同步技術:

解決多個線程爭搶同一個資源的情況,線程協作工作。一份數據同一時刻只能有一個線程處理。

 

解決線程同步的幾種方法:

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

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM