Python多執行緒與GIL鎖

2023-04-09 12:01:24

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()