一文讀懂Python多線程

一文讀懂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

退出主線程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市梆暮,隨后出現(xiàn)的幾起案子服协,更是在濱河造成了極大的恐慌,老刑警劉巖啦粹,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件偿荷,死亡現(xiàn)場離奇詭異,居然都是意外死亡唠椭,警方通過查閱死者的電腦和手機(jī)跳纳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贪嫂,“玉大人寺庄,你說我怎么就攤上這事。” “怎么了斗塘?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵赢织,是天一觀的道長。 經(jīng)常有香客問我馍盟,道長于置,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任贞岭,我火速辦了婚禮八毯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瞄桨。我一直安慰自己宪彩,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布讲婚。 她就那樣靜靜地躺著洛心,像睡著了一般漓帅。 火紅的嫁衣襯著肌膚如雪睛蛛。 梳的紋絲不亂的頭發(fā)上将宪,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音物赶,去河邊找鬼白指。 笑死,一個胖子當(dāng)著我的面吹牛酵紫,可吹牛的內(nèi)容都是我干的告嘲。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼奖地,長吁一口氣:“原來是場噩夢啊……” “哼橄唬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起参歹,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤仰楚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后犬庇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體僧界,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年臭挽,在試婚紗的時候發(fā)現(xiàn)自己被綠了捂襟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡欢峰,死狀恐怖葬荷,靈堂內(nèi)的尸體忽然破棺而出涨共,到底是詐尸還是另有隱情,我是刑警寧澤闯狱,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站抛计,受9級特大地震影響哄孤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜吹截,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一瘦陈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧波俄,春花似錦晨逝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至冬念,卻和暖如春趁窃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背急前。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工醒陆, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人裆针。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓刨摩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親世吨。 傳聞我的和親對象是個殘疾皇子澡刹,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容