介紹
執行緒(Thread)是作業系統能夠進行運算排程的最小單位。它被包含在程式中,是程式中的實際運作單位。一個程式中可以併發多個執行緒,每條執行緒並行執行不同的任務。同一程式中的多個執行緒共享程式中的全部系統資源。
python中的多執行緒因為全域性直譯器鎖GIL的原因限制同一時刻只能由一個執行緒執行,無法發揮多核CPU的優勢【當然這是在預設直譯器CPython中的缺陷,在JPython中沒有GIL的問題,以下基於CPython直譯器】。所以:GIL並不是python的特性,python也不依賴於GIL。說了那麼多,那麼python中的多執行緒是不是沒用了?
答案當然不是。這取決於執行緒執行的場景是什麼?是做計算(計算密集型)還是輸入輸出(I/O密集型),針對不同的場景使用不同的方法。多核心CPU可以有多個核心並行完成計算,所以多核提升的是計算效能,但每個CPU一旦遇到I/O阻塞,仍然需要等待,所以多核對I/O密集型任務沒有太大的提升,多執行緒適合處理I/0密集型程式,如檔案讀寫,web請求,資料庫請求等。
使用
以下演示使用多執行緒對一個變數值進行修改,在迴圈的次數不多時修改後變數的值是符合預期的,當增加迴圈次數後,變數最終的值並不符合預期。由此可見:執行緒之間資源是存在競爭的,修改同一份資源必須加互斥鎖,同時需要避免死鎖。
# coding=utf-8
import threading
# 定義一個欄位。多執行緒執行+1操作
balance = 0
def worker1():
global balance
for i in range(1000):
balance += 1
print('執行緒1執行完成,balance='+str(balance))
def worker2():
global balance
for i in range(1000):
balance += 1
print('執行緒2執行完成,balance='+str(balance))
def main():
# 構造執行緒物件
t1 = threading.Thread(target=worker1)
t2 = threading.Thread(target=worker2)
# 開始執行
t1.start()
t2.start()
"""
迴圈次數為1000時,程式輸出:
執行緒1執行完成,balance=1000
執行緒2執行完成,balance=2000
迴圈次數為1000000時,程式輸出:
執行緒1執行完成,balance=1180919
執行緒2執行完成,balance=1179703
"""
if __name__ == '__main__':
main()
要想解決以下的問題,需要使用執行緒的鎖物件,只需要對worker1和woker2方法進行修改。
# 建立一個互斥鎖,預設是未鎖定狀態
mutex = threading.Lock()
def worker1():
global balance
for i in range(1000000):
mutex.acquire()
balance += 1
mutex.release()
print('執行緒1執行完成,balance=' + str(balance))
def worker2():
global balance
for i in range(1000000):
mutex.acquire()
balance += 1
mutex.release()
print('執行緒2執行完成,balance=' + str(balance))
"""
加了互斥鎖之後的輸出:
執行緒1執行完成,balance=1941343
執行緒2執行完成,balance=2000000
"""
特點:
- 執行緒執行的順序是不確定的
- 主執行緒【程式】會等待所有子執行緒結束後才會退出,主執行緒【程式】結束麼子執行緒必然結束
- 執行緒間共享資源
- 修改資源必要時需要加鎖,同時避免死鎖
- 佔用的資源比程式少
- 執行緒並不是越多越快
- 由於GIL的原因,多執行緒並不是真正的併發,只是交替執行
本作品採用《CC 協議》,轉載必須註明作者和本文連結