python併發:對執行緒的介紹
Python有大量不同的併發模組,例如threading,queues和multiprocessing。threading模組是實現併發的主要途徑。幾年前,multiprocessing模組被加到python的標準庫中。但這篇文章集中討論threading模組。
入門
我們先用一個簡單例子來說明執行緒是如何工作的。我們建立一個Thread類的子類,並把它的名字輸出到標準輸出(stdout)裡。讓我們開始吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
import random import time from threading import Thread ######################################################################## class MyThread(Thread): """ A threading example """ #---------------------------------------------------------------------- def __init__(self, name): """Initialize the thread""" Thread.__init__(self) self.name = name self.start() #---------------------------------------------------------------------- def run(self): """Run the thread""" amount = random.randint(3, 15) time.sleep(amount) msg = "%s has finished!" % self.name print(msg) #---------------------------------------------------------------------- def create_threads(): """ Create a group of threads """ for i in range(5): name = "Thread #%s" % (i+1) my_thread = MyThread(name=name) if __name__ == "__main__": create_threads() |
在上面的程式碼中,我們引入了Python的random模組,time模組並且從threading模組中引入了Thread類。接著建立一個Thread類的子類,並重寫它的__init__方法,讓其接收一個名為name的引數。為了開啟一個執行緒,你必須呼叫它的start()方法,所以我們在init方法最後呼叫它。當你開啟一個執行緒時,它會自動呼叫run()方法。我們重寫了它的run方法,讓它選擇一個隨機時間數去休眠。例子中的random.randint讓python隨機選擇3到15之間的一個數,然後我們讓執行緒休眠這麼多秒來模擬執行緒在做一些事情。最後我們將執行緒名稱列印出來讓使用者知道執行緒已經完成。
create_threads方法建立了5個執行緒,分別給了獨一無二的名稱。如果你執行這段程式碼,你將看到類似如下的結果:
1 2 3 4 5 |
Thread #2 has finished! Thread #1 has finished! Thread #3 has finished! Thread #4 has finished! Thread #5 has finished! |
每次輸出的順序都會不同。試著執行幾次程式碼觀察順序的變化。
寫一個多執行緒的下載器
相對之前的例子,一個工具程式能更好地解釋執行緒是如何工作的。所以在這個例子中,我們建立了一個可以從網上下載檔案的Thread類。美國國稅局有大量給美國公民用來記稅的PDF表單。我們用這個免費的資源來舉例,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# Use this version for Python 2 import os import urllib2 from threading import Thread ######################################################################## class DownloadThread(Thread): """ A threading example that can download a file """ #---------------------------------------------------------------------- def __init__(self, url, name): """Initialize the thread""" Thread.__init__(self) self.name = name self.url = url #---------------------------------------------------------------------- def run(self): """Run the thread""" handle = urllib2.urlopen(self.url) fname = os.path.basename(self.url) with open(fname, "wb") as f_handler: while True: chunk = handle.read(1024) if not chunk: break f_handler.write(chunk) msg = "%s has finished downloading %s!" % (self.name, self.url) print(msg) #---------------------------------------------------------------------- def main(urls): """ Run the program """ for item, url in enumerate(urls): name = "Thread %s" % (item+1) thread = DownloadThread(url, name) thread.start() if __name__ == "__main__": urls = ["http://www.irs.gov/pub/irs-pdf/f1040.pdf", "http://www.irs.gov/pub/irs-pdf/f1040a.pdf", "http://www.irs.gov/pub/irs-pdf/f1040ez.pdf", "http://www.irs.gov/pub/irs-pdf/f1040es.pdf", "http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"] main(urls) |
這基本上是對第一版的完全重寫。在這個例子中,我們引用了os、urllib2和threading模組。我們在thread類裡使用urllib2模組來下載檔案。使用os模組提取下載檔案的檔名,這樣我們可以在自己電腦上建立同名檔案。在DownloadThread類中,我們讓__init__接收一個url和一個執行緒名。在run方法中,我們訪問url,抽取檔名,並使用該檔名在磁碟上命名或建立一個檔案。然後我們使用while迴圈以每次1KB的速度下載檔案並把它寫進磁碟。一旦檔案完成儲存,我們列印出執行緒的名稱和完成下載的url。
升級
這個程式碼的Python3版本略微有些不同。你必須引用urllib,並使用urllib.request.urlopen代替urllib2urlopen。這是python3的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# Use this version for Python 3 import os import urllib.request from threading import Thread ######################################################################## class DownloadThread(Thread): """ A threading example that can download a file """ #---------------------------------------------------------------------- def __init__(self, url, name): """Initialize the thread""" Thread.__init__(self) self.name = name self.url = url #---------------------------------------------------------------------- def run(self): """Run the thread""" handle = urllib.request.urlopen(self.url) fname = os.path.basename(self.url) with open(fname, "wb") as f_handler: while True: chunk = handle.read(1024) if not chunk: break f_handler.write(chunk) msg = "%s has finished downloading %s!" % (self.name, self.url) print(msg) #---------------------------------------------------------------------- def main(urls): """ Run the program """ for item, url in enumerate(urls): name = "Thread %s" % (item+1) thread = DownloadThread(url, name) thread.start() if __name__ == "__main__": urls = ["http://www.irs.gov/pub/irs-pdf/f1040.pdf", "http://www.irs.gov/pub/irs-pdf/f1040a.pdf", "http://www.irs.gov/pub/irs-pdf/f1040ez.pdf", "http://www.irs.gov/pub/irs-pdf/f1040es.pdf", "http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"] main(urls) |
結語
現在你從理論和實踐上了解了如何使用執行緒。當你建立使用者介面並想保持介面的可用性時,執行緒就特別有用。沒有執行緒,使用者介面將變得遲鈍,當你下載一個大檔案或者執行一個龐大的資料庫查詢命令時使用者介面會長時間無響應。為了防止這樣情況發生,你可以使用多執行緒來處理執行時間長的程式並且在完成後返回介面進行互動。