Python多執行緒機制

2022-01-30 08:56:07 字數 4757 閱讀 5638

今天要跟大家一起來學習一下python的多執行緒機制。有兩個原因,其一是自己在學習中經常會使用到多執行緒,其二當然是自己對python中的多執行緒並不是很了解。那麼,今天和大家一起了解下~

python多執行緒機制

開發多執行緒的應用系統,是在日常開發中經常會遇到的需求。同時,python也為多執行緒系統的開發提供了很好的支援。

大家應該都知道,python多執行緒機制是在gil(global interpreter lock)全域性解釋鎖的基礎上建立的。

那麼python為什麼需要全域性解釋鎖?

為什麼需要全域性解釋鎖?

我們知道,要支援多執行緒的話,乙個基本的要求就是不同執行緒對共享資源訪問的互斥,所以python中引入了gil,當然這是第乙個原因。

python中的gil是乙個非常霸道的互斥實現,在乙個執行緒擁有了直譯器的訪問權之後,其它的所有執行緒都必須等待它釋放直譯器的訪問權,即使這些執行緒的下一條指令並不會互相影響。

這樣的說法也就意味著,無論如何,在同一時間,只能有乙個執行緒能訪問python提供的api。因為單處理器的本質是不可能並行的,這裡的同一時間確實對於單處理器是毫無意義的,但是對於多處理器,同一時間,確實可以有多個時間獨立執行。然而正是由於gil限制了這樣的情形,使得多處理器最終退化為單處理器,效能大打折扣。那麼,為什麼還要使用gil呢?這裡就要提到第二個原因。

當然,python社群也早都認識到了這個問題,並且在不斷探索,greg stein和mark hammond兩位老兄曾經建立過乙份去除gil的branch,但是很不幸,這個分支在很多的基準測試中,尤其是在單執行緒的測試上,效率只有使用gil的一半左右。

使用gil時,保護機制的粒度比較大,也就是我們似乎只需要將可能被多個執行緒共享的資源保護起來即可,對於不會被多個執行緒共享的資源,完全可以不用保護。但是,如果使用更細粒度的鎖機制進行保護,那麼,會導致大量的加鎖和解鎖功能,加鎖和解鎖對於作業系統來說,是乙個比較重量級的動作,同時,沒有gil的保護,編寫python的擴充套件模組的難度也大大增加。

所以,目前為止,gil仍然是多執行緒機制的基石。

對於python而言,位元組碼直譯器是python的核心所在,所以python通過gil來互斥不同執行緒對直譯器的使用。這裡舉個例子進行說明:

假設,現在有三個執行緒a、b和c,它們都需要直譯器來執行位元組碼,進行對應的計算,那麼在這之前,它們必須獲得gil。那麼現在假設執行緒a獲得了gil,其它執行緒只能等a釋放gil之後,才能獲得。

對!是這樣沒錯,於是,有兩個問題:

1. 執行緒a何時釋放gil呢(如果a使用完直譯器之後才釋放gil,那麼,並行的計算退化為序列,多執行緒的意義何在?)

2. 執行緒b和c誰將在a釋放gil之後獲得gil呢?

所以毫無疑問的,python擁有其自己的一套執行緒排程機制。

關於執行緒排程

和作業系統的程序排程一樣,執行緒排程機制主要解決兩個問題:

1. 在何時掛起當前執行緒,選擇處於等待狀態的下乙個執行緒?

2. 在眾多處於等待狀態的執行緒中,應該選擇啟用哪個執行緒?

對於何時進行執行緒排程的問題,是由python自身決定的。我們可以聯想作業系統進行程序切換的問題,當乙個程序執行了一段時間之後,發生了時鐘中斷,於是作業系統響應時鐘中斷,並在這時開始程序的排程。

與此類似,python中通過軟體模擬了這樣的中斷,來啟用執行緒的排程。python的位元組碼直譯器是按照指令的順序一條一條的順序執行從而工作的,python內部維護著這樣乙個數值,作為python內部的時鐘,假設這個值為n,那麼python將在執行了n條指令之後立刻啟動執行緒排程機制。

也就是說,當乙個執行緒獲得gil後,python內部的監測機制就開始啟動,當這個執行緒執行了n條指令後,python直譯器將強制掛起當前執行緒,開始切換到下乙個處於等待狀態的執行緒。

在python中,可以這樣獲得這個數值(n):

那麼,下乙個問題,python會在眾多等待的執行緒中選擇哪乙個呢?

答案是,不知道。因為這個問題是交給了底層的作業系統來解決的,python借用了底層作業系統所提供的執行緒排程機制來決定下乙個獲得gil進入直譯器的執行緒是誰。

所以說,python中的執行緒實際上就是作業系統所支援的原生執行緒。

那麼,接下來,我們一起揭開python中gil的真實面目。

關於gil

應該知道,python中多執行緒常用的兩個模組:thread和在其之上的threading。其中thread是使用c實現的,而threading是用python實現。

我們可以通過thread模組進行分析(以python2.7.13為例)。

建立執行緒

首先從建立執行緒說起,在threadmodule.c中,thread_pythread_start_new_thread()函式通過三個主要的動作完成乙個執行緒的建立:

//

建立bootstate結構

boot = pymem_new(struct bootstate, 1

);if (boot ==null)

return

pyerr_nomemory();

boot->interp = pythreadstate_get()->interp;

boot->func =func;

boot->args =args;

boot->keyw =keyw;

boot->tstate = _pythreadstate_prealloc(boot->interp);

if (boot->tstate ==null)

py_incref(func);

py_incref(args);

py_xincref(keyw);

//初始化多執行緒環境

pyeval_initthreads();

//建立執行緒

ident = pythread_start_new_thread(t_bootstrap, (void*) boot);

if (ident == -1

) return pyint_fromlong(ident);

1. 建立並初始化bootstate結構boot,在boot中,將儲存關於python的一切資訊(執行緒過程,執行緒過程引數等)。

2. 初始化python的多執行緒環境。

3. 以boot為引數,建立作業系統的原生執行緒。

從以上**可以看出,python在剛啟動時,並不支援多執行緒,也就是說,python中支援多執行緒的資料結構以及gil都是沒有建立的。當然這是因為大多數的python程式都不需要python的支援。

在python虛擬機器啟動時,多執行緒機制並沒有被啟用,它只支援單執行緒,一旦使用者呼叫thread.start_new_thread,明確的告訴python虛擬機器需要建立新的執行緒,這時python意識到使用者需要多執行緒的支援,這個時候,python虛擬機會自動建立多執行緒需要的資料結構、環境以及gil。

建立多執行緒環境

建立多執行緒環境,主要就是建立gil。那麼gil是如何實現的呢?

開啟"python/ceval.c":

static pythread_type_lock interpreter_lock = 0; /*

this is the gil

*/static pythread_type_lock pending_lock = 0; /*

for pending calls

*/static

long main_thread = 0

;int

pyeval_threadsinitialized(

void

)void

pyeval_initthreads(

void

)

在這段**中,iterpreter_lock就是gil。

無論建立多少個執行緒,python建立多執行緒環境的動作只會執行一次。在建立gil之前,python會檢查gil是否已經被建立,如果是,則不再進行任何動作,否則,就會去建立這個gil。

在上述**中,我們可以看到,建立gil使用的是pythread_allocate_lock完成的,下面看看該函式的內部實現:

pythread_type_lock

pythread_allocate_lock(

void

)

可以看到該函式返回了alock,alock是結構體pnrmutex,實際上就是我們需要建立的那個interperter_lock(gil)。這麼說來,gil就是結構體pnrmutex呀,於是我們找來它的真身:

typedef struct

nrmutex nrmutex, *pnrmutex ;

這裡又三個變數,owned、thread_id和hevent。這裡的hevent是windows平台下的event這個核心物件,也就是通過event來實現執行緒之間的互斥。thread_id將記錄任一時刻獲得gil的執行緒的id。

那麼owned是什麼呢?

gil中的owned是指示gil是否可用的變數,它的值被初始化為-1,python會檢查這個值是否為1,如果是,則意味著gil可用,必須將其置為0,當owned為0後,表示該gil已經被乙個執行緒占用,不可再用;同時,當乙個執行緒開始等待gil時,其owned就會被增加1;當乙個執行緒最終釋放gil時,一定會將gil的owned減1,這樣,當所有需要gil的執行緒都最終釋放了gil之後,owned將再次變為-1,意味著gil再次變為可用。

關於python中的多執行緒,今天我們就學到這裡。

python多執行緒鎖機制

在多執行緒程式設計中常用到的乙個概念就是鎖,它用於將執行緒需要獨佔的資源進行加鎖,使用後再進行釋放,防止死鎖發生。此處給出乙個不加鎖的多執行緒例子 實現整數n在每個執行緒內加1並列印 usr bin python coding utf 8 import threading import time c...

多執行緒 事件機制

多執行緒 事件機制 即執行緒b的啟動需要等待執行緒a的某個訊號,如果等待到資訊則開始執行,如下,摘自 精通windows api 精通windows api 示例 event.c 7.2.1 演示使用event同步執行緒 標頭檔案 include include 常量定義 define numthr...

JAVA多執行緒機制

1,使用執行緒子類建立執行緒 繼承主題,重寫執行方法。2,使用執行緒類直接建立執行緒物件 用宣告執行緒 生成目標物件,建立目標物件.b b new b 建立執行緒,放入目標物件a new thread b 實現了runnable介面 重寫執行方法 1,啟動 啟動執行緒,只有處於新建狀態下的執行緒才能...