線程操作之鎖的使用


一、線程鎖

1.多個線程搶占資源的情況:

鎖通常被用來實現對共享資源的同步訪問。為每一個共享資源創建一個Lock對象,當你需要訪問該資源時,調用acquire方法來獲取鎖對象(如果其它線程已經獲得了該鎖,則當前線程需等待其被釋放),待資源訪問完后,再調用release方法釋放鎖:

案例一:

from threading import  Thread,Lock
import time
K = Lock()
def func():
    global n
    K.acquire()       # 加鎖
    team = n
    time.sleep(0.1)   # 分析:第一個線程剛拿到team = n = 100,就遇到阻塞,馬上切換到下一個線程,下一個線程還是跟上一個線程一樣的情況,
                      #       由於線程切換速度很快,一直拿到的team都是99,除非你的線程數量足夠大。
                      # 解決方法: 給處理數據部分進行加鎖處理。
    n = team-1
    K.release()      # 釋放鎖
if __name__ == '__main__':
    n = 100
    l = []
    for i in range(100):
        t = Thread(target=func)
        l.append(t)
        t.start()
    for t in l:
        t.join()
    print(n)

案例二:

from threading import Thread,Lock
x = 0
K = Lock()
def func():
    global x
    K.acquire()
    for i in range(60000):

        x = x+1
        # t1 的x剛拿到0 保存狀態 就被切換了
        # t2 的x拿到0 進行+1操作  x = 1
        # t1 又獲得了運行 ,返回被切之前的保存狀態,x=0+1  x= 1
        # 其實運算出來的數字應該是+2,實際上只+1
        # 這就產生了數據安全問題
    K.release()
if __name__ == '__main__':
    t1 = Thread(target=func)
    t2 = Thread(target=func)
    t3 = Thread(target=func)

    t1.start()
    t2.start()
    t3.start()

    t1.join()
    t2.join()
    t3.join()
    print(x)

二、死鎖問題

死鎖: 是指兩個或兩個以上的進程或線程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱為死鎖進程,如下就是死鎖

from threading import Lock as Lock
import time
mutexA=Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()

解決方法,遞歸鎖,在Python中為了支持在同一線程中多次請求同一資源,python提供了可重入鎖RLock。

這個RLock內部維護着一個Lock和一個counter變量,counter記錄了acquire的次數,從而使得資源可以被多次require。直到一個線程所有的acquire都被release,其他的線程才能獲得資源。上面的例子如果使用RLock代替Lock,則不會發生死鎖:

from threading import Thread,RLock,currentThread
import time
k1 = k2 = RLock()   #  一個線程拿到鎖,counter加1,該線程內又碰到加鎖的情況,則counter繼續加1,這期間所有其他線程都只能等待,等待該線程釋放所有鎖,即counter遞減到0為止
class Myt(Thread):
    def run(self):
        self.task1()
        self.task2()

    def task1(self):
        k1.acquire()
        print(f'{self.name} 搶到了 鎖1')
        k2.acquire()
        print(f'{self.name} 搶到了 鎖2')
        k2.release()
        print(f'{self.name} 釋放了 鎖2')
        k1.release()
        print(f'{self.name} 釋放了 鎖1')


    def task2(self):
        k2.acquire()
        print(f'{self.name} 搶到了 鎖2')
        time.sleep(1)     # 當線程1 執行到這一步的時候,遇到阻塞,馬上切換到線程2,此時,線程1已經拿到了鎖2,但是線程2也要拿,所以形成了死鎖。
        k1.acquire()      # 解決方法: 加入遞歸鎖RLock
        print(f'{self.name} 搶到了 鎖1')
        k1.release()
        print(f'{self.name} 釋放了 鎖1')
        k2.release()
        print(f'{self.name} 釋放了 鎖2')

for i in range(3):
    t = Myt()
    t.start()

三、 信號量Semaphore

同進程的一樣

Semaphore管理一個內置的計數器,
每當調用acquire()時內置計數器-1;
調用release() 時內置計數器+1;
計數器不能小於0;當計數器為0時,acquire()將阻塞線程直到其他線程調用release()。

實例:(同時只有5個線程可以獲得semaphore,即可以限制最大連接數為5):

from threading import Thread,currentThread,Semaphore

import time
def func():
    sm.acquire()
    print(f'{currentThread().name} 正在執行')
    time.sleep(3)
    sm.release()
sm = Semaphore(5)
for i in range(15):
    t= Thread(target=func)
    t.start()

四、GIL鎖:

GIL:全局解釋器鎖。每個線程在執行的過程都需要先獲取GIL,保證同一時刻只有一個線程可以執行代碼。

GIL 與Lock是兩把鎖,本質上也是一把互斥鎖,保護的數據不一樣,前者是解釋器級別的(當然保護的就是解釋器級別的數據,比如垃圾回收的數據),后者是保護用戶自己開發的應用程序的數據,很明顯GIL不負責這件事,只能用戶自定義加鎖處理,即Lock

過程分析:所有線程搶的是GIL鎖,或者說所有線程搶的是執行權限

  線程1搶到GIL鎖,拿到執行權限,開始執行,然后加了一把Lock,還沒有執行完畢,即線程1還未釋放Lock,有可能線程2搶到GIL鎖,開始執行,執行過程中發現Lock還沒有被線程1釋放,於是線程2進入阻塞,被奪走執行權限,有可能線程1拿到GIL,然后正常執行到釋放Lock。。。這就導致了串行運行的效果

  既然是串行,那我們執行

  t1.start()

  t1.join

  t2.start()

  t2.join()

  這也是串行執行啊,為何還要加Lock呢,需知join是等待t1所有的代碼執行完,相當於鎖住了t1的所有代碼,而Lock只是鎖住一部分操作共享數據的代碼。

五、計算密集型

在 處理像科學計算 這類需要持續使用cpu的類型。

from multiprocessing import Process
from threading import Thread
import os,time

res = 0
def func():
    global res
    for i in range(10000000):
        res += i


if __name__ == '__main__':

    print(os.cpu_count())   # CPU核心數
    start = time.time()
    l = []
    for i in range(4):
        # t = Process(target=func)
        t =Thread(target=func)
        l.append(t)
        t.start()
    for t in l :
        t.join()
    end = time.time()
    print(end-start)   # 多進程耗時:3.4384214878082275   多線程耗時:4.417709827423096

由上看出:在處理持續計算長時間使用CPU的代碼,多進程處理的速度比多線程快。推薦使用多進程進行運算。

六、IO密集型

指在處理像IO操作等可能引起阻塞的類型

from multiprocessing import Process
from threading import Thread
import time

def func():
    time.sleep(5)

if __name__ == '__main__':
    l = []
    start = time.time()
    for i in range(4):
        # t = Process(target=func)  # 多進程
        t = Thread(target=func)  # 多線程
        l.append(t)
        t.start()
    for t in l:
        t.join()
    end = time.time()
    print(end-start)   # 多進程:5.8953258991241455  多線程:5.002920150756836

由上可以看出:在執行IO操作引起阻塞這種任務的時候,多線程的速度明顯比多進程要快。

由上面兩個案例,得出結論:

​ python對於計算密集型的任務開多線程的效率並不能帶來多大性能上的提升,甚至不如串行(沒有大量切換),但是,對於IO密集型的任務效率還是有顯著提升的。

​ 多線程用於IO密集型,如socket,爬蟲,web
​ 多進程用於計算密集型,如金融分析


免責聲明!

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



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