1. 程式人生 > >提高n倍效率的Python多執行緒

提高n倍效率的Python多執行緒

程序和執行緒是什麼

程序是程式執行的最小單元,每個程序都有自己獨立的記憶體空間,而執行緒是程序的一個實體,是系統呼叫呼叫的一個基本單位。

舉個栗子吧:

我們啟動一個app 這就建立了一個程序,這個app裡可能有語音播放、搜尋等功能,這些是程序裡不同的執行緒。

注意:執行緒是輕量級的,他沒有獨立的空間地址(記憶體空間),因為他是由程序建立的,寄存在程序的記憶體地址中。一個程序會包含多個執行緒(這就是我們今天說的多執行緒)

更多Python視訊、原始碼、資料加群960410445免費獲取

我們先了解一下執行緒的5種狀態:

1、新建狀態:

當一個執行緒被建立時就開始了它的生命週期,在啟動執行緒之前他一直處於新建狀態。

2、就緒狀態:

當執行緒被啟動時,由於還沒有分配到cpu資源,該執行緒進入等待佇列在等待另一個執行緒執行完(等待cpu服務),此時執行緒被稱為就緒狀態。

3、執行狀態:

當處於就緒狀態的執行緒被呼叫並獲得cpu資源時,此時為執行狀態。

4、阻塞狀態:

一個正在執行的執行緒在某些情況下不得已讓出cpu資源時,會中止自己的執行過程,這是被稱為阻塞狀態。

值得注意的是:阻塞被消除後是回到就緒狀態,不是執行狀態。

5、死亡狀態:

執行緒被終止、銷燬、或執行完畢則進入死亡狀態。不可再重新啟動

阻塞狀態的分類

阻塞狀態又分為三種情況:等待阻塞、同步阻塞、其他阻塞

說到阻塞不得不提到一個‘鎖’的概念

多執行緒可以執行多個任務,很大程度上提高了我們程式的工作效率,但是面臨一個非常致命的問題。如果有多個執行緒去操作同一個列表(這個列表被稱為:共享資料),

比如執行緒a要列表第一個元素的值加1,這個過程可以細分為3步:

1.取出元素;

2:元素加1;

3:將最終的結果放入列表。

那如果在a執行緒執行到第二步加1的時候執行緒b突然要讀取列表 那麼他讀取到的列表仍然是沒修改之前的內容。這並不是我們想要的

所以引進了鎖的概念。當某個執行緒需要獨佔共享資源時,必須先上鎖,這樣別的執行緒就無法再操作。當操作完之後一定要將鎖開啟,別的執行緒才可以操作資料。

在I/O密集型操作中,需要保持資料同步的時候需要加鎖 保證資源同步。但同時因為其他執行緒面臨阻塞,效能不可避免的會下降。

  • 同步阻塞:執行緒請求鎖定的時候進入同步阻塞,一旦獲得鎖又變成執行狀態。
  • 等待阻塞:是指等待其他執行緒通知的狀態,執行緒獲得條件鎖定後,呼叫“等待”將進入這個狀態,一旦其他執行緒發出通知,執行緒將進入同步狀態,再次競爭條件鎖定。
  • 其他阻塞:指執行緒sleep 、join或等待io時的阻塞。

下面我們建立一個簡單的多執行緒

python3.x中提供了兩種建立執行緒的方式:

_thread.start_new_thread()

threading.Thread()

提高n倍效率的Python多執行緒

 

從控制檯列印的結果來看 t1執行緒和t2執行緒無規律的交錯列印。這正是兩個執行緒之間搶佔cpu資源的結果。

下面我們模擬多窗口出售電影票的場景來理解阻塞和鎖

提高n倍效率的Python多執行緒

 

通過多次執行程式碼、發現控制檯列印的結果有時候明明兩個視窗都售出去一張票了,但餘票數量相等。

更明顯的是明明美團視窗顯示餘票已經為0了,但是另外兩個視窗還是有很多剩餘電影票 如下圖:

提高n倍效率的Python多執行緒

 

通過分析控制檯記錄我們會發現,美團售票視窗一次性賣了好幾百張票,糯米和淘票視窗的資料一直沒有更新成最新的庫存,導致明明沒票了,缺還顯示剩餘很多。

不僅僅是售票,生活中有很多這樣的例子,比如搶購火車票,銀行取錢等...都會有這種資料不同步的問題。解決這一問題的辦法就是前面提到的‘鎖’。

簡單的講美團在賣票的過程中,將庫存進行鎖定,在這期間糯米和淘票票不可以在操作,只能等待美團操作完將資料更新後,然後釋放鎖才可以繼續操作。

在threading模組中提供了一個獲得執行緒鎖的方法:

threading.Lock()

SHOW CODING!!!

提高n倍效率的Python多執行緒

 

在執行時我們發現,無論執行程式碼多少次,最終票數為0時,所有視窗都停止售票了。這個例子很完美的闡述了阻塞和鎖在多執行緒中的重要性!!!

大家在開發過程中,使用多執行緒也要多加註意,避免不必要的錯誤發生

其他常用方法

1、threading.Rlock()

RLock允許在同一執行緒中被多次acquire。而Lock卻不允許這種情況。

注意:如果使用RLock,那麼acquire和release必須成對出現,即呼叫了n次acquire,必須呼叫n次的release才能真正釋放所佔用的瑣。

2、threading.Condition()

可以把Condiftion理解為一把高階的瑣,它提供了比Lock, RLock更高階的功能,允許我們能夠控制複雜的執行緒同步問題。

threadiong.Condition在內部維護一個瑣物件(預設是RLock),可以在建立Condigtion物件的時候把瑣物件作為引數傳入。

Condition也提供了acquire, release方法,其含義與瑣的acquire, release方法一致,其實它只是簡單的呼叫內部瑣物件的對應的方法而已。

Condition還提供wait方法、notify方法、notifyAll方法

(特別要注意:這些方法只有在佔用瑣(acquire)之後才能呼叫,否則將會報RuntimeError異常)

3 、threading.Semaphore和BoundedSemaphore

Semaphore:Semaphore 在內部管理著一個計數器。

呼叫 acquire() 會使這個計數器 -1,release() 則是+1(可以多次release(),所以計數器的值理論上可以無限).

計數器的值永遠不會小於 0,當計數器到 0 時,再呼叫 acquire() 就會阻塞,直到其他執行緒來呼叫release()

4、join()

如果一個執行緒在執行過程中要呼叫另外一個執行緒,並且等到其完成以後才能接著執行

提高n倍效率的Python多執行緒

 

5、isAlive

isAlive 等價於 is_alive(self),用於判斷執行緒是否執行。

當執行緒沒有呼叫start時,或者執行緒執行完畢處於死亡狀態,isAlive()返回false。

提高n倍效率的Python多執行緒

 

6、Daemon

Python主程式當且僅當不存在非Daemon執行緒存活時退出。

即:主程式等待所有非Daemon執行緒結束後才退出,且退出時會自動結束(很粗魯的結束)所有Daemon執行緒。

7、name

提高n倍效率的Python多執行緒