一文讀懂Python多線程
1边涕、線程和進(jìn)程
計算機(jī)的核心是CPU,它承擔(dān)了所有的計算任務(wù)褂微。它就像一座工廠功蜓,時刻在運(yùn)行。
假定工廠的電力有限宠蚂,一次只能供給一個車間使用式撼。也就是說,一個車間開工的時候求厕,其他車間都必須停工著隆。背后的含義就是扰楼,單個CPU一次只能運(yùn)行一個任務(wù)。
進(jìn)程就好比工廠的車間美浦,它代表CPU所能處理的單個任務(wù)弦赖。任一時刻,CPU總是運(yùn)行一個進(jìn)程浦辨,其他進(jìn)程處于非運(yùn)行狀態(tài)蹬竖。
一個車間里,可以有很多工人流酬。他們協(xié)同完成一個任務(wù)币厕。
線程就好比車間里的工人。一個進(jìn)程可以包括多個線程芽腾。
車間的空間是工人們共享的旦装,比如許多房間是每個工人都可以進(jìn)出的。這象征一個進(jìn)程的內(nèi)存空間是共享的摊滔,每個線程都可以使用這些共享內(nèi)存阴绢。
可是,每間房間的大小不同艰躺,有些房間最多只能容納一個人呻袭,比如廁所。里面有人的時候描滔,其他人就不能進(jìn)去了。這代表一個線程使用某些共享內(nèi)存時踪古,其他線程必須等它結(jié)束含长,才能使用這一塊內(nèi)存。
一個防止他人進(jìn)入的簡單方法伏穆,就是門口加一把鎖拘泞。先到的人鎖上門,后到的人看到上鎖枕扫,就在門口排隊陪腌,等鎖打開再進(jìn)去。這就叫"互斥鎖"(Mutual exclusion烟瞧,縮寫 Mutex)诗鸭,防止多個線程同時讀寫某一塊內(nèi)存區(qū)域。
還有些房間参滴,可以同時容納n個人强岸,比如廚房梗逮。也就是說邀层,如果人數(shù)大于n戚嗅,多出來的人只能在外面等著。這好比某些內(nèi)存區(qū)域涕癣,只能供給固定數(shù)目的線程使用。
這時的解決方法谷遂,就是在門口掛n把鑰匙檐迟。進(jìn)去的人就取一把鑰匙,出來時再把鑰匙掛回原處悯衬。后到的人發(fā)現(xiàn)鑰匙架空了弹沽,就知道必須在門口排隊等著了。這種做法叫做"信號量"(Semaphore)甚亭,用來保證多個線程不會互相沖突贷币。
不難看出,mutex是semaphore的一種特殊情況(n=1時)亏狰。也就是說役纹,完全可以用后者替代前者。但是暇唾,因為mutex較為簡單促脉,且效率高,所以在必須保證資源獨占的情況下策州,還是采用這種設(shè)計瘸味。
2、多線程與多進(jìn)程
從上面關(guān)于線程和進(jìn)程的的通俗解釋來看够挂,多線程和多進(jìn)程的含義如下:
多進(jìn)程:允許多個任務(wù)同時進(jìn)行
多線程:允許單個任務(wù)分成不同的部分運(yùn)行
3旁仿、Python多線程編程
3.1 單線程
在好些年前的MS-DOS時代,操作系統(tǒng)處理問題都是單任務(wù)的孽糖,我想做聽音樂和看電影兩件事兒枯冈,那么一定要先排一下順序。
from time import ctime,sleep
def music():
? ?for i in range(2):
? ? ? ?print "I was listening to music. %s" %ctime()
? ? ? ?sleep(1)
def move():
? ?for i in range(2):
? ? ? ?print "I was at the movies! %s" %ctime()
? ? ? ?sleep(5)
if __name__ == '__main__':
? ?music()
? ?move()
? ?print "all over %s" %ctime()
我們先聽了一首音樂办悟,通過for循環(huán)來控制音樂的播放了兩次尘奏,每首音樂播放需要1秒鐘,sleep()來控制音樂播放的時長病蛉。接著我們又看了一場電影炫加,
每一場電影需要5秒鐘,因為太好看了铺然,所以我也通過for循環(huán)看兩遍俗孝。在整個休閑娛樂活動結(jié)束后,我通過
print "all over %s" %ctime()
看了一下當(dāng)前時間魄健,差不多該睡覺了驹针。
運(yùn)行結(jié)果:
I was listening to music. Thu Apr 17 10:47:08 2014
I was listening to music. Thu Apr 17 10:47:09 2014
I was at the movies! Thu Apr 17 10:47:10 2014
I was at the movies! Thu Apr 17 10:47:15 2014
all over Thu Apr 17 10:47:20 2014
其實,music()和move()更應(yīng)該被看作是音樂和視頻播放器诀艰,至于要播放什么歌曲和視頻應(yīng)該由我們使用時決定柬甥。所以饮六,我們對上面代碼做了改造:
import threading
from time import ctime,sleep
def music(func):
? ?for i in range(2):
? ? ? ?print ("I was listening to %s. %s" %(func,ctime()))
? ? ? ?sleep(1)
def move(func):
? ?for i in range(2):
? ? ? ?print ("I was at the %s! %s" %(func,ctime()))
? ? ? ?sleep(5)
if __name__ == '__main__':
? ?music(u'愛情買賣')
? ?move(u'阿凡達(dá)')
? ?print ("all over %s" %ctime())
運(yùn)行結(jié)果:
I was listening to 愛情買賣. Thu Apr 17 11:48:59 2014
I was listening to 愛情買賣. Thu Apr 17 11:49:00 2014
I was at the 阿凡達(dá)! Thu Apr 17 11:49:01 2014
I was at the 阿凡達(dá)! Thu Apr 17 11:49:06 2014
all over Thu Apr 17 11:49:11 2014
3.2 多線程
Python3 通過兩個標(biāo)準(zhǔn)庫 _thread (python2中是thread模塊)和 threading 提供對線程的支持。
_thread 提供了低級別的苛蒲、原始的線程以及一個簡單的鎖卤橄,它相比于 threading 模塊的功能還是比較有限的。
3.2.1使用_thread模塊
調(diào)用_thread模塊中的start_new_thread()函數(shù)來產(chǎn)生新線程臂外。
先用一個實例感受一下:
import _thread
import time
# 為線程定義一個函數(shù)
def print_time(threadName, delay):
? ?count = 0
? ?while count < 5:
? ? ? ?time.sleep(delay)
? ? ? ?count += 1
? ? ? ?print ("%s: %s" % (threadName, time.ctime(time.time())))
# 創(chuàng)建兩個線程
try:
? ?_thread.start_new_thread(print_time, ("Thread-1", 2,))
? ?_thread.start_new_thread(print_time, ("Thread-2", 4,))
except:
? ?print ("Error: unable to start thread")
while 1:
? pass
print ("Main Finished")
代碼輸出為:
Thread-1: Thu Aug 10 16:35:47 2017
Thread-2: Thu Aug 10 16:35:49 2017
Thread-1: Thu Aug 10 16:35:49 2017
Thread-1: Thu Aug 10 16:35:51 2017
Thread-2: Thu Aug 10 16:35:53 2017
Thread-1: Thu Aug 10 16:35:53 2017
Thread-1: Thu Aug 10 16:35:55 2017
Thread-2: Thu Aug 10 16:35:57 2017
Thread-2: Thu Aug 10 16:36:01 2017
注意到窟扑,在主線程寫了:
while 1:
? pass
這是讓主線程一直在等待.
如果去掉上面兩行,那就直接輸出并結(jié)束程序執(zhí)行:
"Main Finished"
3.2.2使用threading模塊
threading 模塊除了包含 _thread 模塊中的所有方法外漏健,還提供的其他方法:
threading.currentThread(): 返回當(dāng)前的線程變量嚎货。
threading.enumerate(): 返回一個包含正在運(yùn)行的線程的list。正在運(yùn)行指線程啟動后蔫浆、結(jié)束前殖属,不包括啟動前和終止后的線程。
threading.activeCount(): 返回正在運(yùn)行的線程數(shù)量瓦盛,與len(threading.enumerate())有相同的結(jié)果洗显。
除了使用方法外,線程模塊同樣提供了Thread類來處理線程原环,Thread類提供了以下方法:
run(): 用以表示線程活動的方法挠唆。
start():啟動線程活動。
join([time]): 等待至線程中止嘱吗。這阻塞調(diào)用線程直至線程的join() 方法被調(diào)用中止-正常退出或者拋出未處理的異常-或者是可選的超時發(fā)生玄组。
isAlive(): 返回線程是否活動的。
getName(): 返回線程名谒麦。
setName(): 設(shè)置線程名俄讹。
直接創(chuàng)建線程
接上面的聽音樂和看電影的例子,我們可以直接使用threading.Thread 創(chuàng)建線程弄匕,并指定執(zhí)行的方法以及傳遞的參數(shù):
import threading
from time import ctime,sleep
def music(func):
? ?for i in range(2):
? ? ? ?print ("I was listening to %s. %s" %(func,ctime()))
? ? ? ?sleep(1)
def move(func):
? ?for i in range(2):
? ? ? ?print ("I was at the %s! %s" %(func,ctime()))
? ? ? ?sleep(5)
threads = []
t1 = threading.Thread(target=music,args=(u'愛情買賣',))
threads.append(t1)
t2 = threading.Thread(target=move,args=(u'阿凡達(dá)',))
threads.append(t2)
if __name__ == '__main__':
? ?for t in threads:
? ? ? ?t.start()
? ?print ("all over %s" %ctime())
結(jié)果輸出為:
I was listening to 愛情買賣. Thu Aug 10 16:57:12 2017
I was at the 阿凡達(dá)! Thu Aug 10 16:57:12 2017
all over Thu Aug 10 16:57:12 2017
I was listening to 愛情買賣. Thu Aug 10 16:57:13 2017
I was at the 阿凡達(dá)! Thu Aug 10 16:57:17 2017
構(gòu)造線程類
我們也可以通過直接從 threading.Thread 繼承創(chuàng)建一個新的子類颅悉,并實例化后調(diào)用 start() 方法啟動新線程沽瞭,即它調(diào)用了線程的 run() 方法:
#!/usr/bin/python3
import threading
import time
exitFlag = 0
class myThread (threading.Thread):
? ?def __init__(self, threadID, name, counter):
? ? ? ?threading.Thread.__init__(self)
? ? ? ?self.threadID = threadID
? ? ? ?self.name = name
? ? ? ?self.counter = counter
? ?def run(self):
? ? ? ?print ("開始線程:" + self.name)
? ? ? ?print_time(self.name, self.counter, 5)
? ? ? ?print ("退出線程:" + self.name)
def print_time(threadName, delay, counter):
? ?while counter:
? ? ? ?if exitFlag:
? ? ? ? ? ?threadName.exit()
? ? ? ?time.sleep(delay)
? ? ? ?print ("%s: %s" % (threadName, time.ctime(time.time())))
? ? ? ?counter -= 1
# 創(chuàng)建新線程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# 開啟新線程
thread1.start()
thread2.start()
print ("退出主線程")
結(jié)果輸出為:
開始線程:Thread-1
開始線程:Thread-2
退出主線程
Thread-1: Thu Aug 10 16:48:41 2017
Thread-2: Thu Aug 10 16:48:42 2017
Thread-1: Thu Aug 10 16:48:42 2017
Thread-1: Thu Aug 10 16:48:43 2017
Thread-2: Thu Aug 10 16:48:44 2017
Thread-1: Thu Aug 10 16:48:44 2017
Thread-1: Thu Aug 10 16:48:45 2017
退出線程:Thread-1
Thread-2: Thu Aug 10 16:48:46 2017
Thread-2: Thu Aug 10 16:48:48 2017
Thread-2: Thu Aug 10 16:48:50 2017
退出線程:Thread-2
從結(jié)果可以看到迁匠,為什么我們開啟了兩個線程之后,主線程立即退出了驹溃?因為我們沒有使用join方法城丧,對于主線程來說,thread1和thread2是子線程豌鹤,使用join方法亡哄,會讓主線程等待子線程執(zhí)行解說再繼續(xù)執(zhí)行。
join()方法
我們修改一下代碼:
#!/usr/bin/python3
import threading
import time
exitFlag = 0
class myThread (threading.Thread):
? ?def __init__(self, threadID, name, counter):
? ? ? ?threading.Thread.__init__(self)
? ? ? ?self.threadID = threadID
? ? ? ?self.name = name
? ? ? ?self.counter = counter
? ?def run(self):
? ? ? ?print ("開始線程:" + self.name)
? ? ? ?print_time(self.name, self.counter, 5)
? ? ? ?print ("退出線程:" + self.name)
def print_time(threadName, delay, counter):
? ?while counter:
? ? ? ?if exitFlag:
? ? ? ? ? ?threadName.exit()
? ? ? ?time.sleep(delay)
? ? ? ?print ("%s: %s" % (threadName, time.ctime(time.time())))
? ? ? ?counter -= 1
# 創(chuàng)建新線程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# 開啟新線程
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print ("退出主線程")
結(jié)果就變?yōu)椋?/p>
開始線程:Thread-1
開始線程:Thread-2
Thread-1: Thu Aug 10 16:52:07 2017
Thread-2: Thu Aug 10 16:52:08 2017
Thread-1: Thu Aug 10 16:52:08 2017
Thread-1: Thu Aug 10 16:52:09 2017
Thread-2: Thu Aug 10 16:52:10 2017
Thread-1: Thu Aug 10 16:52:10 2017
Thread-1: Thu Aug 10 16:52:11 2017
退出線程:Thread-1
Thread-2: Thu Aug 10 16:52:12 2017
Thread-2: Thu Aug 10 16:52:14 2017
Thread-2: Thu Aug 10 16:52:16 2017
退出線程:Thread-2
退出主線程
可以看到 退出主線程 在最后才被打印出來布疙。
setDaemon()方法
有一個方法常常拿來與join方法做比較蚊惯,那就是setDaemon()方法愿卸。我們首先來看一下setDaemon()方法的使用效果:
#!/usr/bin/python3
import threading
import time
exitFlag = 0
class myThread (threading.Thread):
? ?def __init__(self, threadID, name, counter):
? ? ? ?threading.Thread.__init__(self)
? ? ? ?self.threadID = threadID
? ? ? ?self.name = name
? ? ? ?self.counter = counter
? ?def run(self):
? ? ? ?print ("開始線程:" + self.name)
? ? ? ?print_time(self.name, self.counter, 5)
? ? ? ?print ("退出線程:" + self.name)
def print_time(threadName, delay, counter):
? ?while counter:
? ? ? ?if exitFlag:
? ? ? ? ? ?threadName.exit()
? ? ? ?time.sleep(delay)
? ? ? ?print ("%s: %s" % (threadName, time.ctime(time.time())))
? ? ? ?counter -= 1
# 創(chuàng)建新線程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# 開啟新線程
thread1.setDaemon(True)
thread2.setDaemon(True)
thread1.start()
thread2.start()
print ("退出主線程")
結(jié)果輸出為:
開始線程:Thread-1
開始線程:Thread-2
退出主線程
可以看到,在主線程結(jié)束之后截型,程序就終止了趴荸,也就是說兩個子線程也被終止了,這就是setDaemon方法的作用宦焦。主線程A中发钝,創(chuàng)建了子線程B,并且在主線程A中調(diào)用了B.setDaemon(),這個的意思是波闹,把主線程A設(shè)置為守護(hù)線程酝豪,這時候,要是主線程A執(zhí)行結(jié)束了精堕,就不管子線程B是否完成,一并和主線程A退出.這就是setDaemon方法的含義孵淘,這基本和join是相反的。此外锄码,還有個要特別注意的:必須在start() 方法調(diào)用之前設(shè)置夺英,如果不設(shè)置為守護(hù)線程,程序會被無限掛起滋捶。
兩個疑問
我們剛才介紹了兩種使用多線程的方式痛悯,一種是直接調(diào)用threading.Thread 創(chuàng)建線程,另一種是從 threading.Thread 繼承創(chuàng)建一個新的子類重窟,并實例化后調(diào)用 start() 方法啟動進(jìn)程载萌。學(xué)到這里,我就拋出了兩個疑問巡扇,為什么第一種方法中我們可以為不同的線程指定運(yùn)行的方法扭仁,而第二種我們都運(yùn)行的是同一個方法,那么它內(nèi)部的實現(xiàn)機(jī)制是什么呢厅翔?第二個疑問是乖坠,第二種方法中,我們沒有實例化start()方法刀闷,那么run和start這兩個方法的聯(lián)系是什么呢熊泵?
首先,start方法和run方法的關(guān)系如下:用start方法來啟動線程甸昏,真正實現(xiàn)了多線程運(yùn)行顽分,這時無需等待run方法體代碼執(zhí)行完畢而直接繼續(xù)執(zhí)行下面的代碼。通過調(diào)用Thread類的start()方法來啟動一個線程施蜜,這時此線程處于就緒(可運(yùn)行)狀態(tài)卒蘸,并沒有運(yùn)行,一旦得到cpu時間片翻默,就開始執(zhí)行run()方法缸沃,這里方法 run()稱為線程體恰起,它包含了要執(zhí)行的這個線程的內(nèi)容,Run方法運(yùn)行結(jié)束趾牧,此線程隨即終止村缸。
而run()方法的源碼如下,可以看到武氓,如果我們指定了target即線程執(zhí)行的函數(shù)的話梯皿,run方法可以轉(zhuǎn)而調(diào)用那個函數(shù),如果沒有的話县恕,將不執(zhí)行东羹,而我們在自定義的Thread類里面重寫了這個run 方法,所以程序會執(zhí)行這一段忠烛。
? ?def run(self):
? ? ? ?"""Method representing the thread's activity.
? ? ? ?You may override this method in a subclass. The standard run() method
? ? ? ?invokes the callable object passed to the object's constructor as the
? ? ? ?target argument, if any, with sequential and keyword arguments taken
? ? ? ?from the args and kwargs arguments, respectively.
? ? ? ?"""
? ? ? ?try:
? ? ? ? ? ?if self._target:
? ? ? ? ? ? ? ?self._target(*self._args, **self._kwargs)
? ? ? ?finally:
? ? ? ? ? ?# Avoid a refcycle if the thread is running a function with
? ? ? ? ? ?# an argument that has a member that points to the thread.
? ? ? ? ? ?del self._target, self._args, self._kwargs
線程同步
如果多個線程共同對某個數(shù)據(jù)修改属提,則可能出現(xiàn)不可預(yù)料的結(jié)果,為了保證數(shù)據(jù)的正確性美尸,需要對多個線程進(jìn)行同步冤议。
使用 Thread 對象的 Lock 和 Rlock 可以實現(xiàn)簡單的線程同步,這兩個對象都有 acquire 方法和 release 方法师坎,對于那些需要每次只允許一個線程操作的數(shù)據(jù)恕酸,可以將其操作放到 acquire 和 release 方法之間。如下:
多線程的優(yōu)勢在于可以同時運(yùn)行多個任務(wù)(至少感覺起來是這樣)胯陋。但是當(dāng)線程需要共享數(shù)據(jù)時蕊温,可能存在數(shù)據(jù)不同步的問題。
考慮這樣一種情況:一個列表里所有元素都是0遏乔,線程"set"從后向前把所有元素改成1义矛,而線程"print"負(fù)責(zé)從前往后讀取列表并打印。
那么盟萨,可能線程"set"開始改的時候凉翻,線程"print"便來打印列表了,輸出就成了一半0一半1捻激,這就是數(shù)據(jù)的不同步制轰。為了避免這種情況,引入了鎖的概念铺罢。
鎖有兩種狀態(tài)——鎖定和未鎖定艇挨。每當(dāng)一個線程比如"set"要訪問共享數(shù)據(jù)時残炮,必須先獲得鎖定韭赘;如果已經(jīng)有別的線程比如"print"獲得鎖定了,那么就讓線程"set"暫停势就,也就是同步阻塞泉瞻;等到線程"print"訪問完畢脉漏,釋放鎖以后,再讓線程"set"繼續(xù)袖牙。
經(jīng)過這樣的處理侧巨,打印列表時要么全部輸出0,要么全部輸出1鞭达,不會再出現(xiàn)一半0一半1的尷尬場面司忱。
實例:
#!/usr/bin/python3
import threading
import time
class myThread (threading.Thread):
? ?def __init__(self, threadID, name, counter):
? ? ? ?threading.Thread.__init__(self)
? ? ? ?self.threadID = threadID
? ? ? ?self.name = name
? ? ? ?self.counter = counter
? ?def run(self):
? ? ? ?print ("開啟線程: " + self.name)
? ? ? ?# 獲取鎖,用于線程同步
? ? ? ?threadLock.acquire()
? ? ? ?print_time(self.name, self.counter, 3)
? ? ? ?# 釋放鎖畴蹭,開啟下一個線程
? ? ? ?threadLock.release()
def print_time(threadName, delay, counter):
? ?while counter:
? ? ? ?time.sleep(delay)
? ? ? ?print ("%s: %s" % (threadName, time.ctime(time.time())))
? ? ? ?counter -= 1
threadLock = threading.Lock()
threads = []
# 創(chuàng)建新線程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# 開啟新線程
thread1.start()
thread2.start()
# 添加線程到線程列表
threads.append(thread1)
threads.append(thread2)
# 等待所有線程完成
for t in threads:
? ?t.join()
print ("退出主線程")
輸出為:
開啟線程: Thread-1
開啟線程: Thread-2
Thread-1: Thu Aug 10 20:45:59 2017
Thread-1: Thu Aug 10 20:46:00 2017
Thread-1: Thu Aug 10 20:46:01 2017
Thread-2: Thu Aug 10 20:46:03 2017
Thread-2: Thu Aug 10 20:46:05 2017
Thread-2: Thu Aug 10 20:46:07 2017
退出主線程
線程優(yōu)先級隊列( Queue)
Python 的 Queue 模塊中提供了同步的坦仍、線程安全的隊列類,包括FIFO(先入先出)隊列Queue叨襟,LIFO(后入先出)隊列LifoQueue繁扎,和優(yōu)先級隊列 PriorityQueue。
這些隊列都實現(xiàn)了鎖原語糊闽,能夠在多線程中直接使用梳玫,可以使用隊列來實現(xiàn)線程間的同步。
Queue 模塊中的常用方法:
Queue.qsize() 返回隊列的大小
Queue.empty() 如果隊列為空右犹,返回True,反之False
Queue.full() 如果隊列滿了提澎,返回True,反之False
Queue.full 與 maxsize 大小對應(yīng)
Queue.get([block[, timeout]])獲取隊列,timeout等待時間
Queue.get_nowait() 相當(dāng)Queue.get(False)
Queue.put(item) 寫入隊列念链,timeout等待時間
Queue.put_nowait(item) 相當(dāng)Queue.put(item, False)
Queue.task_done() 在完成一項工作之后虱朵,Queue.task_done()函數(shù)向任務(wù)已經(jīng)完成的隊列發(fā)送一個信號
Queue.join() 實際上意味著等到隊列為空,再執(zhí)行別的操作
#!/usr/bin/python3
import queue
import threading
import time
exitFlag = 0
class myThread (threading.Thread):
? ?def __init__(self, threadID, name, q):
? ? ? ?threading.Thread.__init__(self)
? ? ? ?self.threadID = threadID
? ? ? ?self.name = name
? ? ? ?self.q = q
? ?def run(self):
? ? ? ?print ("開啟線程:" + self.name)
? ? ? ?process_data(self.name, self.q)
? ? ? ?print ("退出線程:" + self.name)
def process_data(threadName, q):
? ?while not exitFlag:
? ? ? ?queueLock.acquire()
? ? ? ?if not workQueue.empty():
? ? ? ? ? ?data = q.get()
? ? ? ? ? ?queueLock.release()
? ? ? ? ? ?print ("%s processing %s" % (threadName, data))
? ? ? ?else:
? ? ? ? ? ?queueLock.release()
? ? ? ?time.sleep(1)
threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = queue.Queue(10)
threads = []
threadID = 1
# 創(chuàng)建新線程
for tName in threadList:
? ?thread = myThread(threadID, tName, workQueue)
? ?thread.start()
? ?threads.append(thread)
? ?threadID += 1
# 填充隊列
queueLock.acquire()
for word in nameList:
? ?workQueue.put(word)
queueLock.release()
# 等待隊列清空
while not workQueue.empty():
? ?pass
# 通知線程是時候退出
exitFlag = 1
# 等待所有線程完成
for t in threads:
? ?t.join()
print ("退出主線程")
上面的代碼每次執(zhí)行的結(jié)果是不一樣的钓账,取決于哪個進(jìn)程先獲得鎖碴犬,一次運(yùn)行的輸出如下:
開啟線程:Thread-1
開啟線程:Thread-2
開啟線程:Thread-3
Thread-2 processing One
Thread-3 processing Two
Thread-1 processing Three
Thread-3 processing Four
Thread-1 processing Five
退出線程:Thread-3
退出線程:Thread-2
退出線程:Thread-1
退出主線程