來自:Python空間(微訊號:Devtogether) 專註Python 程式設計,推送各種 Python 基礎/進階文章,資料分析,爬蟲實戰,資料結構與演演算法等
本文字數:2678 字 閱讀本文大概需要:7 分鐘
00.寫在之前
大家好,我是 Rocky0429,今天我來寫一下 Python 中的多執行緒。在正式開始之前,我先用比較通俗的語言給大家介紹幾個比較重要的概念。
首先是「併發程式設計」。「併發」其實在我們的生活中隨處可見,比如我們去銀行存錢,銀行如果只有一個視窗並且辦業務的人又比較多,那麼肯定就是排成一個長長的隊伍,這樣的體驗對我們來說肯定是非常差的。那麼該如何解決這個問題呢?那就是多開幾個視窗,把人流分散開,這樣就減短了我們隊伍的長度,減少整體排隊的時間。
上面是我們顯示生活中遇到的問題,其實在計算機中我們也會遇到這樣的問題,那麼我們是怎麼解決的呢?其實是這樣,每次我們執行一個 Python 程式,這個執行中的程式我們稱它為「行程」,如果我們想讓它快一點,能夠像我們上面例子中多開幾個視窗併發解決問題一樣,我們就要在行程裡引入一個更小的東西,那就是「執行緒」。
我來舉一個具體的例子,比如我們每天都會用的微信,每次執行它的時候會產生一個行程,估計大家都碰到過這種情況,就是你在和別人開影片的時候,另外有一個人給你發訊息,我們都知道這個時候可以把影片的視窗縮小然後回覆另一個人的訊息,能完成這樣的操作,其實就是微信裡的多個執行緒幫助我們完成了這件事。
在編寫 Python 程式的時候我們也會遇到這種“同時”的需求,同時有大量的請求過來,要我們同時對它們進行處理,那麼這個處理的方法,就是「多執行緒」程式設計。
01.建立「執行緒」
Python 的標準庫中自帶了多執行緒相關的模組,使在 python 中建立執行緒成了一件很簡單的事。與執行緒相關的模組一共有兩個:thread 和 threading。一般情況下我們只需要 threading 即可。
下麵我來寫一個簡單的使用多執行緒的例子:
import threading
def func():
print('Hello World')
def main():
for i in range(4):
t = threading.Thread(target=func)
t.start()
if __name__ == '__main__':
main()
上面的程式碼中,我定義了一個 func 函式,然後在 main 函式中透過 for 迴圈建立了 4 個執行緒,然後透過將 target = func 的方式去告訴執行緒執行 func 函式,一切就緒後呼叫執行緒的 start 方法執行執行緒。結果如下:
Hello World
Hello World
Hello World
Hello World
這個結果看起來和我們直接用 for 迴圈列印四次 Hello World 沒什麼區別,其實區別還是有的,就是肉眼看不出來而已,下麵我來改造一下上面的程式:
import threading
import time
def func():
print('Hello World')
time.sleep(1)
def main():
for i in range(4):
t = threading.Thread(target=func)
t.start()
if __name__ == '__main__':
main()
上面我加了 time.sleep(1),如果只是用 for 迴圈的話,這個程式至少得執行 4 秒,但是由於我們用的是執行緒併發執行,其實整個程式只需要花費 1 秒多就可以執行完畢,你可以自行嘗試一下,可以 sleep 的時間長點自行體驗一下。
當然了,如果你自己不樂意動手,作為關愛讀者成長協會的會長,我這還有一個好的辦法,且待我再改造一下:
import threading
import time
from threading import current_thread
def func():
print(current_thread().getName(),'start')
print('Hello World')
time.sleep(1)
print(current_thread().getName(), 'end')
def main():
for i in range(4):
t = threading.Thread(target=func)
t.start()
if __name__ == '__main__':
main()
上面的改造中取用了 current_thread,對當前執行狀態進行一個顯示,你可以很好的看到執行緒在執行中的一些過程,執行結果如下所示:
Thread-1 start
Hello World
Thread-2 start
Hello World
Thread-3 start
Hello World
Thread-4 start
Hello World
Thread-1 end
Thread-4 end
Thread-3 end
Thread-2 end
02.執行緒傳參
在上面建立執行緒的例子其實是過於簡單了,在我們實際的程式設計中給程式傳遞引數是必不可少的,下麵我在之前例子的基礎上,寫一個傳遞引數的例子:
import threading
def func(cnt, name):
for i in range(cnt):
print('Hello {}'.format(name))
def main():
names = ['Rocky', 'leey', 'cp3', 'chen']
for i in range(4):
t = threading.Thread(target=func, args=(10, names[i]))
t.start()
if __name__ == '__main__':
main()
上面的程式中,我讓 func 接受了兩個引數,在 main 函式中定義了一個 names 的串列,之後在建立執行緒的時候將 names 中的元素傳遞給不同的執行緒。由上可以看出在 Python 中執行緒傳遞引數也是一件很簡單的事,傳遞的引數都是呼叫 args,透過元組的形式進行。
03.寫在之後
其實很多人認為 Python 的多執行緒是一個相當“雞肋”的東西,因為標準的 Python 系統中使用了 GIL(全域性直譯器鎖),它的作用是避免 Python 直譯器中的執行緒問題,這樣造成了在任意時刻只有一個執行緒在執行 Python 程式碼,這樣就“糟蹋”了計算機「多核」的特性。
誠然,“糟蹋”了多核,這樣對 CPU 密集型的程式來說,Python 多執行緒確實沒有什麼提升,反而會更慢,但我們的程式其實也不是無時無刻在“動彈”的,它們也要等待資源的下載,等待檔案的讀寫,等待使用者的輸入等等等等,這類操作我們統一稱為 I/O 操作,對於這類,才是真正顯示 Python 多執行緒能力的時候。