Python多執行緒與GIL鎖

智健發表於2023-04-09

Python多執行緒與GIL鎖

python多執行緒

Python的多執行緒程式設計可以在單個程式內建立多個執行緒來同時執行多個任務,從而提高程式的效率和效能。Python的多執行緒實現依賴於作業系統的執行緒排程器,並且受到全域性直譯器鎖(GIL)的限制,因此在某些情況下,多執行緒並不能真正實現並行執行。

import threading

def print_numbers():
    for i in range(1, 6):
        print(i)

thread1 = threading.Thread(target=print_numbers)
thread2 = threading.Thread(target=print_numbers)

thread1.start()
thread2.start()

thread1.join()
thread2.join()

print("Done")

上述程式碼建立了兩個執行緒分別同時去列印1-5數字,但是即使有多個cpu,同一時刻也只能列印一個數字!why?

​ 這是由於Python中的全域性直譯器鎖(GIL)導致的。GIL是一種機制,用於確保在任何給定時間內,只有一個執行緒在Python直譯器中執行位元組碼。這意味著無論有多少個CPU核心,每個執行緒都無法並行執行Python位元組碼。因此在執行CPU密集型任務時,多個執行緒之間的執行是交替進行的,而不是並行的。因此對於CPU任務,python的多執行緒是假的。而對於IO任務,確實是真正的多執行緒。

GIL鎖

GIL的存在主要是為了防止Python直譯器中的資料結構被多個執行緒同時修改,導致資料結構出現不一致的情況。透過限制同一時刻只有一個執行緒能夠執行Python位元組碼,GIL可以確保Python直譯器中的資料結構不會被多個執行緒同時修改,從而保證執行緒安全。

IO任務

I/O資源(Input/Output resources),是指計算機系統中用於輸入輸出資料的裝置和介面,例如硬碟、網路介面、鍵盤、滑鼠等。在計算機程式設計中,I/O操作指的是程式和外部裝置之間進行的資料傳輸和互動,如讀取檔案、網路傳輸,爬蟲等。I/O操作通常是非常耗時的,因為它們需要等待外部裝置響應或者等待資料的讀取。

這些IO任務通常只佔用記憶體和網路,不佔用CPU資源,所以也不佔用python直譯器,因此如果是IO密集型任務,python的多執行緒優勢才能體現出來,而CPU密集型任務python的多執行緒效率無法有效提升。

破解GIL鎖的限制

如果需要處理CPU密集型任務,可以考慮使用多程式程式設計,因為在多程式中,每個程式都有自己的直譯器和記憶體空間,從而避免了GIL的限制。可以充分利用多核處理器(必須是真正的多核處理器才能體現,否則還是單程式)的優勢。

python多程式

使用Python多程式程式設計的一般步驟如下:

  1. 匯入multiprocessing模組,建立程式池物件。可以透過Pool()函式建立程式池物件,指定最大程式數。
  2. 定義需要執行的任務函式。這個函式應該能夠接受任務引數,處理任務,返回任務結果。
  3. 呼叫程式池物件的map()函式,傳入任務函式和任務引數。該函式會將任務引數分配給程式池中的程式執行,並返回任務結果列表。
  4. 處理任務結果。根據任務函式的返回值,對任務結果進行處理。可以使用Python中的其他模組,如pandas、numpy等進行資料處理或結果視覺化。

需要注意的是,在Python中使用多程式程式設計時,程式之間的通訊和同步是需要考慮的問題。Python中的multiprocessing模組提供了一些同步原語,如Lock、Semaphore等,用於控制程式之間的訪問。此外,也可以使用Python中的Queue模組實現程式之間的通訊。

程式碼示例:

import multiprocessing

def worker(num):
    """任務函式"""
    print('Worker %d is running' % num)
    return num**2

if __name__ == '__main__':
    # 建立程式池物件
    pool = multiprocessing.Pool(processes=4)
    # 任務引數列表
    nums = [1, 2, 3, 4, 5]
    # 執行任務並獲取結果
    results = pool.map(worker, nums)
    print(results)

python多程式與多執行緒的結合

每個程式用多個執行緒執行,這樣可以在每個程式內部實現並行處理IO任務,同時也可以充分利用多核處理器的優勢。

程式碼示例:

import multiprocessing
import threading

def worker(num):
    """執行緒函式,用於處理任務"""
    print(f"Worker {num} is running...")

def main():
    """主函式,建立多個程式和執行緒"""
    # 建立3個程式
    processes = []
    for i in range(3):
        p = multiprocessing.Process(target=process_worker, args=(i,))
        processes.append(p)
        p.start()

    # 在每個程式內部建立2個執行緒
    for p in processes:
        for i in range(2):
            t = threading.Thread(target=worker, args=(i,))
            t.start()

if __name__ == '__main__':
    main()

相關文章