為了引入多執行緒的概念,讓我們來看乙個例子。假設你想安排一些**,在一段延遲後或在特定時間執行。可以在程式啟動時新增如下**:
import time, datetime
starttime = datetime.datetime(2029, 10, 31, 0, 0, 0)
while datetime.datetime.now() < starttime:
time.sleep(1)
print('program now starting on halloween 2029')
--snip--
這段**指定 2029 年 10 月 31 日作為開始時間,不斷呼叫 time.sleep(1),直到開始時間。在等待 time.sleep()的迴圈呼叫完成時,程式不能做任何事情,它只是坐在那裡,直到 2029 年萬聖節。這是因為 python 程式在預設情況下,只有乙個執行執行緒。要理解什麼是執行執行緒,就要回憶第 2 章關於控制流的討論,當時你想象程式的執行就像把手指放在一行**上,然後移動到下一行,或是流控制語句讓它去的任何地方。單執行緒程式只有乙個「手指」。但多執行緒的程式有多個「手指」。每個「手指」仍然移動到控制流語句定義的下一行**,但這些「手指」可以在程式的不同地方,同時執行不同的**行(到目前為止,本書所有的程式一直是單執行緒的)。不必讓所有的**等待,直到 time.sleep()函式完成,你可以使用 python 的threading 模組,在單獨的執行緒中執行延遲或安排的**。這個單獨的執行緒將因為time.sleep()呼叫而暫停。同時,程式可以在原來的執行緒中做其他工作。要得到單獨的執行緒,首先要呼叫 threading.thread()函式,生成乙個 thread 物件。在新的檔案中輸入以下**,並儲存為 threaddemo.py:
import threading, time
print('start of program.')
def takeanap():
time.sleep(5)
print('wake up!')
threadobj = threading.thread(target=takeanap)
threadobj.start()
print('end of program.')
我們定義了乙個函式,希望用於新執行緒中。為了建立乙個 thread 物件,我們呼叫 threading.thread(),並傳入關鍵字引數 target=takeanap。這意味著我們要在新執行緒中呼叫的函式是 takeanap()。請注意,關鍵字引數是 target=takeanap,而不是 target=takeanap()。這是因為你想將 takeanap()函式本身作為引數,而不是呼叫 takeanap(),並傳入它的返回值。我們將 threading.thread()建立的 thread 物件儲存在 threadobj 中,然後呼叫threadobj.start(),建立新的執行緒,並開始在新執行緒中執行目標函式。如果執行該程式,輸出將像這樣:
start of program.
end of program.
wake up!
這可能有點令人困惑。如果 print('end of program.')是程式的最後一行,你可能會認為,它應該是最後列印的內容。 wake up!在它後面是因為,當 threadobj.start()被呼叫時, threadobj 的目標函式執行在乙個新的執行執行緒中。將它看成是第二根「手指」,出現在 takeanap()函式開始處。主線程繼續 print('end of program.')。同時,新執行緒已執行了 time.sleep(5)呼叫,暫停 5 秒鐘。之後它從 5 秒鐘小睡中醒來,列印了'wakeup!',然後從 takeanap()函式返回。按時間順序, 'wake up!'是程式最後列印的內容。通常,程式在檔案中最後一行**執行後終止(或呼叫 sys.exit())。但threaddemo.py 有兩個執行緒。第乙個是最初的執行緒,從程式開始處開始,在 print('endof program.')後結束。第二個執行緒是呼叫 threadobj.start()時建立的,始於 takeanap()函式的開始處,在 takeanap()返回後結束。在程式的所有執行緒終止之前, python 程式不會終止。在執行 threaddemo.py 時,即使最初的執行緒已經終止,第二個執行緒仍然執行 time.sleep(5)呼叫。
向執行緒的目標函式傳遞引數
如果想在新執行緒中執行的目標函式有引數,可以將目標函式的引數傳入threading.thread()。 例如,假設想在自己的執行緒中執行以下 print()呼叫:
>>> print('cats', 'dogs', 'frogs', sep=' & ')
cats & dogs & frogs
該 print()呼叫有 3 個常規引數: 'cats'、 'dogs'和'frogs',以及乙個關鍵字引數: sep=' & '。常規引數可以作為乙個列表,傳遞給 threading.thread()中的 args 關鍵字引數。關鍵字引數可以作為乙個字典,傳遞給 threading.thread()中的 kwargs 關鍵字引數。在互動式環境中輸入以下**:
>>> import threading
>>> threadobj = threading.thread(target=print, args=['cats', 'dogs', 'frogs'],
kwargs=)
>>> threadobj.start()
cats & dogs & frogs
為了確保引數'cats'、 'dogs'和'frogs'傳遞給新執行緒中的 print(),我們將 args=['cats','dogs', 'frogs']傳入 threading.thread()。為了確保關鍵字引數 sep=' & '傳遞給新執行緒中的 print(),我們將 kwargs=傳入 threading.thread()。threadobj.start()呼叫將建立乙個新執行緒來呼叫 print()函式,它會傳入'cats'、'dogs'和'frogs'作為引數,以及' & '作為 sep 關鍵字引數。下面建立新執行緒呼叫 print()的方法是不正確的:
threadobj = threading.thread(target=print('cats', 'dogs', 'frogs', sep=' & '))
這行**最終會呼叫 print()函式,將它的返回值(print()的返回值總是無)作為target 關鍵字引數。它沒有傳遞 print()函式本身。如果要向新執行緒中的函式傳遞引數,就使用 threading.thread()函式的 args 和 kwargs 關鍵字引數。
併發問題
可以輕鬆地建立多個新執行緒,讓它們同時執行。但多執行緒也可能會導致所謂的併發問題。如果這些執行緒同時讀寫變數,導致互相干擾,就會發生併發問題。併發問題可能很難一致地重現,所以難以除錯。多執行緒程式設計本身就是乙個廣泛的主題,超出了本書的範圍。必須記住的是:為了避免併發問題,絕不讓多個執行緒讀取或寫入相同的變數。當建立乙個新的 thread 物件時,要確保其目標函式只使用該函式中的區域性變數。這將避免程式中難以除錯的併發問題
在 有關於多執行緒程式設計的初學者教程。
python多執行緒 python多執行緒
通常來說,多程序適用於計算密集型任務,多執行緒適用於io密集型任務,如網路爬蟲。關於多執行緒和多程序的區別,請參考這個 下面將使用python標準庫的multiprocessing包來嘗試多執行緒的操作,在python中呼叫多執行緒要使用multiprocessing.dummy,如果是多程序則去掉...
python多執行緒詳解 Python多執行緒詳解
前言 由於最近的工作中一直需要用到python去處理資料,而在面對大量的資料時,python多執行緒的優勢就展現出來了。因而藉此機會,盡可能詳盡地來闡述python多執行緒。但對於其更底層的實現機制,在此不做深究,僅是對於之前的一知半解做個補充,也希望初學者能夠通過這篇文章,即便是照葫蘆畫瓢,也能夠...
python程式多執行緒 PYTHON多執行緒
在單執行緒的情況下,程式是逐條指令順序執行的。同一時間只做乙個任務,完成了乙個任務再進行下乙個任務。比如有5個人吃飯,單執行緒一次只允許乙個人吃,乙個人吃完了另乙個人才能接著吃,假如每個人吃飯都需要1分鐘,5個人就需要5分鐘。多執行緒的情況下,程式就會同時進行多個任務,雖然在同一時刻也只能執行某個任...