線程

線程

引言&動機(jī)

考慮一下這個(gè)場景,我們有10000條數(shù)據(jù)需要處理,處理每條數(shù)據(jù)需要花費(fèi)1秒,但讀取數(shù)據(jù)只需要0.1秒,每條數(shù)據(jù)互不干擾。該如何執(zhí)行才能花費(fèi)時(shí)間最短呢?

在多線程(MT)編程出現(xiàn)之前,電腦程序的運(yùn)行由一個(gè)執(zhí)行序列組成,執(zhí)行序列按順序在主機(jī)的中央處理器(CPU)中運(yùn)行。無論是任務(wù)本身要求順序執(zhí)行還是整個(gè)程序是由多個(gè)子任務(wù)組成,程序都是按這種方式執(zhí)行的。即使子任務(wù)相互獨(dú)立,互相無關(guān)(即,一個(gè)子任務(wù)的結(jié)果不影響其它子 任務(wù)的結(jié)果)時(shí)也是這樣。

對于上邊的問題,如果使用一個(gè)執(zhí)行序列來完成,我們大約需要花費(fèi)10000*0.1 + 10000 = 11000 秒。這個(gè)時(shí)間顯然是太長了。

那我們有沒有可能在執(zhí)行計(jì)算的同時(shí)取數(shù)據(jù)呢?或者是同時(shí)處理幾條數(shù)據(jù)呢?如果可以,這樣就能大幅提高任務(wù)的效率。這就是多線程編程的目的。

對于本質(zhì)上就是異步的,需要有多個(gè)并發(fā)事務(wù),各個(gè)事務(wù)的運(yùn)行順序可以是不確定的,隨機(jī)的,不可預(yù)測的問題,多線程是最理想的解決方案。這樣的任務(wù)可以被分成多個(gè)執(zhí)行流,每個(gè)流都有一個(gè)要完成的目標(biāo),然后將得到的結(jié)果合并,得到最終的結(jié)果。

線程和進(jìn)程

什么是進(jìn)程

進(jìn)程(有時(shí)被稱為重量級進(jìn)程)是程序的一次 執(zhí)行。每個(gè)進(jìn)程都有自己的地址空間,內(nèi)存,數(shù)據(jù)棧以及其它記錄其運(yùn)行軌跡的輔助數(shù)據(jù)。操作系 統(tǒng)管理在其上運(yùn)行的所有進(jìn)程,并為這些進(jìn)程公平地分配時(shí)間。進(jìn)程也可以通過 fork 和 spawn 操作 來完成其它的任務(wù)。不過各個(gè)進(jìn)程有自己的內(nèi)存空間,數(shù)據(jù)棧等,所以只能使用進(jìn)程間通訊(IPC), 而不能直接共享信息。

什么是線程

線程(有時(shí)被稱為輕量級進(jìn)程)跟進(jìn)程有些相似,不同的是,所有的線程運(yùn)行在同一個(gè)進(jìn)程中, 共享相同的運(yùn)行環(huán)境。它們可以想像成是在主進(jìn)程或“主線程”中并行運(yùn)行的“迷你進(jìn)程”。

線程有開始,順序執(zhí)行和結(jié)束三部分。它有一個(gè)自己的指令指針,記錄自己運(yùn)行到什么地方。線程的運(yùn)行可能被搶占(中斷),或暫時(shí)的被掛起(也叫睡眠),讓其它的線程運(yùn)行,這叫做讓步。 一個(gè)進(jìn)程中的各個(gè)線程之間共享同一片數(shù)據(jù)空間,所以線程之間可以比進(jìn)程之間更方便地共享數(shù)據(jù)以及相互通訊。

當(dāng)然,這樣的共享并不是完全沒有危險(xiǎn)的。如果多個(gè)線程共同訪問同一片數(shù)據(jù),則由于數(shù)據(jù)訪問的順序不一樣,有可能導(dǎo)致數(shù)據(jù)結(jié)果的不一致的問題。這叫做競態(tài)條件(race condition)。

線程一般都是并發(fā)執(zhí)行的,不過在單CPU 的系統(tǒng)中,真正的并發(fā)是不可能的,每個(gè)線程會被安排成每次只運(yùn)行一小會,然后就把 CPU 讓出來,讓其它的線程去運(yùn)行。由于有的函數(shù)會在完成之前阻塞住,在沒有特別為多線程做修改的情 況下,這種“貪婪”的函數(shù)會讓 CPU 的時(shí)間分配有所傾斜。導(dǎo)致各個(gè)線程分配到的運(yùn)行時(shí)間可能不 盡相同,不盡公平。

Python、線程和全局解釋器鎖

全局解釋器鎖(GIL)

首先需要明確的一點(diǎn)是GIL并不是Python的特性,它是在實(shí)現(xiàn)Python解析器(CPython)時(shí)所引入的一個(gè)概念。就好比C++是一套語言(語法)標(biāo)準(zhǔn),但是可以用不同的編譯器來編譯成可執(zhí)行代碼。同樣一段代碼可以通過CPython,PyPy,Psyco等不同的Python執(zhí)行環(huán)境來執(zhí)行(其中的JPython就沒有GIL)。

那么CPython實(shí)現(xiàn)中的GIL又是什么呢?GIL全稱Global Interpreter Lock為了避免誤導(dǎo),我們還是來看一下官方給出的解釋:

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

盡管Python完全支持多線程編程, 但是解釋器的C語言實(shí)現(xiàn)部分在完全并行執(zhí)行時(shí)并不是線程安全的。 實(shí)際上,解釋器被一個(gè)全局解釋器鎖保護(hù)著,它確保任何時(shí)候都只有一個(gè)Python線程執(zhí)行。

在多線程環(huán)境中,Python 虛擬機(jī)按以下方式執(zhí)行:

設(shè)置GIL

切換到一個(gè)線程去執(zhí)行

運(yùn)行

指定數(shù)量的字節(jié)碼指令

線程主動讓出控制(可以調(diào)用time.sleep(0))

把線程設(shè)置完睡眠狀態(tài)

解鎖GIL

再次重復(fù)以上步驟

對所有面向I/O 的(會調(diào)用內(nèi)建的操作系統(tǒng) C 代碼的)程序來說,GIL 會在這個(gè) I/O 調(diào)用之 前被釋放,以允許其它的線程在這個(gè)線程等待 I/O 的時(shí)候運(yùn)行。如果某線程并未使用很多 I/O 操作, 它會在自己的時(shí)間片內(nèi)一直占用處理器(和 GIL)。也就是說,I/O 密集型的 Python 程序比計(jì)算密集 型的程序更能充分利用多線程環(huán)境的好處。

退出線程

當(dāng)一個(gè)線程結(jié)束計(jì)算,它就退出了。線程可以調(diào)用thread.exit()之類的退出函數(shù),也可以使用 Python 退出進(jìn)程的標(biāo)準(zhǔn)方法,如 sys.exit()或拋出一個(gè) SystemExit 異常等。不過,你不可以直接 “殺掉”(“kill”)一個(gè)線程。

在Python 中使用線程

在Win32 和 Linux, Solaris, MacOS, *BSD 等大多數(shù)類 Unix 系統(tǒng)上運(yùn)行時(shí),Python 支持多線程 編程。Python 使用 POSIX 兼容的線程,即 pthreads。

默認(rèn)情況下,只要在解釋器中

>> import thread

如果沒有報(bào)錯(cuò),則說明線程可用。

Python 的 threading 模塊

Python 供了幾個(gè)用于多線程編程的模塊,包括 thread, threading 和 Queue 等。thread 和 threading 模塊允許程序員創(chuàng)建和管理線程。thread 模塊 供了基本的線程和鎖的支持,而 threading 供了更高級別,功能更強(qiáng)的線程管理的功能。Queue 模塊允許用戶創(chuàng)建一個(gè)可以用于多個(gè)線程之間 共享數(shù)據(jù)的隊(duì)列數(shù)據(jù)結(jié)構(gòu)。

核心示:避免使用 thread 模塊

出于以下幾點(diǎn)考慮,我們不建議您使用thread 模塊。

更高級別的threading 模塊更為先 進(jìn),對線程的支持更為完善,而且使用 thread 模塊里的屬性有可能會與 threading 出現(xiàn)沖突。其次, 低級別的 thread 模塊的同步原語很少(實(shí)際上只有一個(gè)),而 threading 模塊則有很多。

對于你的進(jìn)程什么時(shí)候應(yīng)該結(jié)束完全沒有控制,當(dāng)主線程結(jié)束時(shí),所有的線程都會被強(qiáng)制結(jié)束掉,沒有警告也不會有正常的清除工作。我們之前說過,至少threading 模塊能確保重要的子線程退出后進(jìn)程才退出。

thread 模塊

除了產(chǎn)生線程外,thread 模塊也提供了基本的同步數(shù) 據(jù)結(jié)構(gòu)鎖對象(lock object,也叫原語鎖,簡單鎖,互斥鎖,互斥量,二值信號量)。

thread 模塊函數(shù)

start_new_thread(function, args, kwargs=None):產(chǎn)生一個(gè)新的線程,在新線程中用指定的參數(shù)和可選的 kwargs 來調(diào)用這個(gè)函數(shù)。

allocate_lock():分配一個(gè) LockType 類型的鎖對象

exit():讓線程退出

acquire(wait=None):嘗試獲取鎖對象

locked():如果獲取了鎖對象返回 True,否則返回 False

release():釋放鎖

start_new_thread()要求一定要有前兩個(gè)參數(shù)。所以,就算我們想要運(yùn)行的函數(shù)不要參數(shù),也要傳一個(gè)空的元組。

為什么要加上sleep(6)這一句呢? 因?yàn)椋绻覀儧]有讓主線程停下來,那主線程就會運(yùn)行下一條語句,顯示 “all done”,然后就關(guān)閉運(yùn)行著 loop()和 loop1()的兩個(gè)線程,退出了。

我們有沒有更好的辦法替換使用sleep() 這種不靠譜的同步方式呢?答案是使用鎖,使用了鎖,我們就可以在兩個(gè)線程都退出之后馬上退出。

import thread

from time importsleep,time

loops=[4,2]

def loop(nloop,nsec,lock):

print('start loop %s at: %s'%(nloop,time()))

sleep(nsec)

print('loop %s done at: %s'%(nloop,time()))

# 每個(gè)線程都會被分配一個(gè)事先已經(jīng)獲得的鎖,在 sleep()的時(shí)間到了之后就釋放 相應(yīng)的鎖以通知主線程,這個(gè)線程已經(jīng)結(jié)束了。

lock.release()

def main():

print('starting at:',time())

locks=[]

nloops=range(len(loops))

foriinnloops:

# 調(diào)用 thread.allocate_lock()函數(shù)創(chuàng)建一個(gè)鎖的列表

lock=thread.allocate_lock()

# 分別調(diào)用各個(gè)鎖的 acquire()函數(shù)獲得, 獲得鎖表示“把鎖鎖上”

lock.acquire()

locks.append(lock)

foriinnloops:

# 創(chuàng)建線程,每個(gè)線程都用各自的循環(huán)號,睡眠時(shí)間和鎖為參數(shù)去調(diào)用 loop()函數(shù)

thread.start_new_thread(loop,(i,loops[i],locks[i]))

foriinnloops:

# 在線程結(jié)束的時(shí)候,線程要自己去做解鎖操作

# 當(dāng)前循環(huán)只是坐在那一直等(達(dá)到暫停主 線程的目的),直到兩個(gè)鎖都被解鎖為止才繼續(xù)運(yùn)行。

whilelocks[i].locked():pass

print('all DONE at:',time())

if__name__=='__main__':

main()

為什么我們不在創(chuàng)建鎖的循環(huán)里創(chuàng)建線程呢?有以下幾個(gè)原因:

我們想到實(shí)現(xiàn)線程的同步,所以要讓“所有的馬同時(shí)沖出柵欄”。

獲取鎖要花一些時(shí)間,如果你的線程退出得“太快”,可能會導(dǎo)致還沒有獲得鎖,線程就已經(jīng)結(jié)束了的情況。

threading 模塊

threading 模塊不僅提供了 Thread 類,還 供了各 種非常好用的同步機(jī)制。

下面是threading 模塊里所有的對象:

Thread: 表示一個(gè)線程的執(zhí)行的對象

Lock: 鎖原語對象(跟 thread 模塊里的鎖對象相同)

RLock: 可重入鎖對象。使單線程可以再次獲得已經(jīng)獲得了的鎖(遞歸鎖定)。

Condition: 條件變量對象能讓一個(gè)線程停下來,等待其它線程滿足了某個(gè)“條件”。 如,狀態(tài)的改變或值的改變。

Event: 通用的條件變量。多個(gè)線程可以等待某個(gè)事件的發(fā)生,在事件發(fā)生后, 所有的線程都會被激活。

Semaphore: 為等待鎖的線程 供一個(gè)類似“等候室”的結(jié)構(gòu)

BoundedSemaphore: 與 Semaphore 類似,只是它不允許超過初始值

Timer: 與 Thread 相似,只是,它要等待一段時(shí)間后才開始運(yùn)行。

守護(hù)線程

另一個(gè)避免使用thread 模塊的原因是,它不支持守護(hù)線程。當(dāng)主線程退出時(shí),所有的子線程不 論它們是否還在工作,都會被強(qiáng)行退出。有時(shí),我們并不期望這種行為,這時(shí),就引入了守護(hù)線程 的概念

threading 模塊支持守護(hù)線程,它們是這樣工作的:守護(hù)線程一般是一個(gè)等待客戶請求的服務(wù)器, 如果沒有客戶 出請求,它就在那等著。如果你設(shè)定一個(gè)線程為守護(hù)線程,就表示你在說這個(gè)線程 是不重要的,在進(jìn)程退出的時(shí)候,不用等待這個(gè)線程退出。

如果你的主線程要退出的時(shí)候,不用等待那些子線程完成,那就設(shè)定這些線程的daemon 屬性。 即,在線程開始(調(diào)用 thread.start())之前,調(diào)用setDaemon()函數(shù)設(shè)定線程的 daemon 標(biāo)志 (thread.setDaemon(True))就表示這個(gè)線程“不重要”

如果你想要等待子線程完成再退出,那就什么都不用做,或者顯式地調(diào)用thread.setDaemon(False)以保證其 daemon 標(biāo)志為 False。你可以調(diào)用thread.isDaemon()函數(shù)來判 斷其 daemon 標(biāo)志的值。新的子線程會繼承其父線程的 daemon 標(biāo)志。整個(gè) Python 會在所有的非守護(hù) 線程退出后才會結(jié)束,即進(jìn)程中沒有非守護(hù)線程存在的時(shí)候才結(jié)束。

Thread 類

Thread類提供了以下方法:

run(): 用以表示線程活動的方法。

start():啟動線程活動。

join([time]): 等待至線程中止。這阻塞調(diào)用線程直至線程的join() 方法被調(diào)用中止-正常退出或者拋出未處理的異常-或者是可選的超時(shí)發(fā)生。

is_alive(): 返回線程是否活動的。

name(): 設(shè)置/返回線程名。

daemon(): 返回/設(shè)置線程的 daemon 標(biāo)志,一定要在調(diào)用 start()函數(shù)前設(shè)置

用Thread 類,你可以用多種方法來創(chuàng)建線程。我們在這里介紹三種比較相像的方法。

創(chuàng)建一個(gè)Thread的實(shí)例,傳給它一個(gè)函數(shù)

創(chuàng)建一個(gè)Thread的實(shí)例,傳給它一個(gè)可調(diào)用的類對象

從Thread派生出一個(gè)子類,創(chuàng)建一個(gè)這個(gè)子類的實(shí)例

下邊是三種不同方式的創(chuàng)建線程的示例:

import threading

from time importsleep,time

loops=[4,2]

def loop(nloop,nsec,lock):

print('start loop %s at: %s'%(nloop,time()))

sleep(nsec)

print('loop %s done at: %s'%(nloop,time()))

# 每個(gè)線程都會被分配一個(gè)事先已經(jīng)獲得的鎖,在 sleep()的時(shí)間到了之后就釋放 相應(yīng)的鎖以通知主線程,這個(gè)線程已經(jīng)結(jié)束了。

def main():

print('starting at:',time())

threads=[]

nloops=range(len(loops))

foriinnloops:

t=threading.Thread(target=loop,args=(i,loops[i]))

threads.append(t)

foriinnloops:

# start threads

threads[i].start()

foriinnloops:

# wait for all

# join()會等到線程結(jié)束,或者在給了 timeout 參數(shù)的時(shí)候,等到超時(shí)為止。

# 使用 join()看上去 會比使用一個(gè)等待鎖釋放的無限循環(huán)清楚一些(這種鎖也被稱為"spinlock")

threads[i].join()# threads to finish

print('all DONE at:',time())

if__name__=='__main__':

main()

與傳一個(gè)函數(shù)很相似的另一個(gè)方法是在創(chuàng)建線程的時(shí)候,傳一個(gè)可調(diào)用的類的實(shí)例供線程啟動的時(shí)候執(zhí)行——這是多線程編程的一個(gè)更為面向?qū)ο蟮姆椒āO鄬τ谝粋€(gè)或幾個(gè)函數(shù)來說,由于類 對象里可以使用類的強(qiáng)大的功能,可以保存更多的信息,這種方法更為靈活

from threading import Thread

from time importsleep,time

loops=[4,2]

classThreadFunc(object):

def __init__(self,func,args,name=""):

self.name=name

self.func=func

self.args=args

def __call__(self):

# 創(chuàng)建新線程的時(shí)候,Thread 對象會調(diào)用我們的 ThreadFunc 對象,這時(shí)會用到一個(gè)特殊函數(shù) __call__()。

self.func(*self.args)

def loop(nloop,nsec):

print('start loop %s at: %s'%(nloop,time()))

sleep(nsec)

print('loop %s done at: %s'%(nloop,time()))

def main():

print('starting at:',time())

threads=[]

nloops=range(len(loops))

foriinnloops:

t=Thread(target=ThreadFunc(loop,(i,loops[i]),loop.__name__))

threads.append(t)

foriinnloops:

# start threads

threads[i].start()

foriinnloops:

# wait for all

# join()會等到線程結(jié)束,或者在給了 timeout 參數(shù)的時(shí)候,等到超時(shí)為止。

# 使用 join()看上去 會比使用一個(gè)等待鎖釋放的無限循環(huán)清楚一些(這種鎖也被稱為"spinlock")

threads[i].join()# threads to finish

print('all DONE at:',time())

if__name__=='__main__':

main()

最后一個(gè)例子介紹如何子類化Thread 類,這與上一個(gè)例子中的創(chuàng)建一個(gè)可調(diào)用的類非常像。使 用子類化創(chuàng)建線程(第 29-30 行)使代碼看上去更清晰明了。

from threading import Thread

from time importsleep,time

loops=[4,2]

classMyThread(Thread):

def __init__(self,func,args,name=""):

super(MyThread,self).__init__()

self.name=name

self.func=func

self.args=args

def getResult(self):

returnself.res

def run(self):

# 創(chuàng)建新線程的時(shí)候,Thread 對象會調(diào)用我們的 ThreadFunc 對象,這時(shí)會用到一個(gè)特殊函數(shù) __call__()。

print'starting',self.name,'at:',time()

self.res=self.func(*self.args)

printself.name,'finished at:',time()

def loop(nloop,nsec):

print('start loop %s at: %s'%(nloop,time()))

sleep(nsec)

print('loop %s done at: %s'%(nloop,time()))

def main():

print('starting at:',time())

threads=[]

nloops=range(len(loops))

foriinnloops:

t=MyThread(loop,(i,loops[i]),loop.__name__)

threads.append(t)

foriinnloops:

# start threads

threads[i].start()

foriinnloops:

# wait for all

# join()會等到線程結(jié)束,或者在給了 timeout 參數(shù)的時(shí)候,等到超時(shí)為止。

# 使用 join()看上去 會比使用一個(gè)等待鎖釋放的無限循環(huán)清楚一些(這種鎖也被稱為"spinlock")

threads[i].join()# threads to finish

print('all DONE at:',time())

if__name__=='__main__':

main()

除了各種同步對象和線程對象外,threading 模塊還 供了一些函數(shù)。

active_count(): 當(dāng)前活動的線程對象的數(shù)量

current_thread(): 返回當(dāng)前線程對象

enumerate(): 返回當(dāng)前活動線程的列表

settrace(func): 為所有線程設(shè)置一個(gè)跟蹤函數(shù)

setprofile(func): 為所有線程設(shè)置一個(gè) profile 函數(shù)

Lock & RLock

原語鎖定是一個(gè)同步原語,狀態(tài)是鎖定或未鎖定。兩個(gè)方法acquire()和release() 用于加鎖和釋放鎖。

RLock 可重入鎖是一個(gè)類似于Lock對象的同步原語,但同一個(gè)線程可以多次調(diào)用。

Lock 不支持遞歸加鎖,也就是說即便在同 線程中,也必須等待鎖釋放。通常建議改 RLock, 它會處理 “owning thread” 和 “recursion level” 狀態(tài),對于同 線程的多次請求鎖 為,只累加計(jì)數(shù)器。每次調(diào) release() 將遞減該計(jì)數(shù)器,直到 0 時(shí)釋放鎖,因此 acquire() 和 release() 必須 要成對出現(xiàn)。

生產(chǎn)者-消費(fèi)者問題和 Queue 模塊

現(xiàn)在我們用一個(gè)經(jīng)典的(生產(chǎn)者消費(fèi)者)例子來介紹一下 Queue模塊。

生產(chǎn)者消費(fèi)者的場景是:生產(chǎn)者生產(chǎn)貨物,然后把貨物放到一個(gè)隊(duì)列之類的數(shù)據(jù)結(jié)構(gòu)中,生產(chǎn)貨物所要花費(fèi)的時(shí)間無法預(yù)先確定。消費(fèi)者消耗生產(chǎn)者生產(chǎn)的貨物的時(shí)間也是不確定的。

常用的Queue 模塊的屬性:

queue(size): 創(chuàng)建一個(gè)大小為size的Queue對象。

qsize(): 返回隊(duì)列的大小(由于在返回的時(shí)候,隊(duì)列可能會被其它線程修改,所以這個(gè)值是近似值)

empty(): 如果隊(duì)列為空返回 True,否則返回 False

full(): 如果隊(duì)列已滿返回 True,否則返回 False

put(item,block=0): 把item放到隊(duì)列中,如果給了block(不為0),函數(shù)會一直阻塞到隊(duì)列中有空間為止

get(block=0): 從隊(duì)列中取一個(gè)對象,如果給了 block(不為 0),函數(shù)會一直阻塞到隊(duì)列中有對象為止

Queue 模塊可以用來進(jìn)行線程間通訊,讓各個(gè)線程之間共享數(shù)據(jù)。

現(xiàn)在,我們創(chuàng)建一個(gè)隊(duì)列,讓生產(chǎn)者(線程)把新生產(chǎn)的貨物放進(jìn)去供消費(fèi)者(線程)使用。

from Queue import Queue

from random import randint

from time importsleep,time

from threading import Thread

classMyThread(Thread):

def __init__(self,func,args,name=""):

super(MyThread,self).__init__()

self.name=name

self.func=func

self.args=args

def getResult(self):

returnself.res

def run(self):

# 創(chuàng)建新線程的時(shí)候,Thread 對象會調(diào)用我們的 ThreadFunc 對象,這時(shí)會用到一個(gè)特殊函數(shù) __call__()。

print'starting',self.name,'at:',time()

self.res=self.func(*self.args)

printself.name,'finished at:',time()

# writeQ()和 readQ()函數(shù)分別用來把對象放入隊(duì)列和消耗隊(duì)列中的一個(gè)對象。在這里我們使用 字符串'xxx'來表示隊(duì)列中的對象。

def writeQ(queue):

print'producing object for Q...'

queue.put('xxx',1)

print"size now",queue.qsize()

def readQ(queue):

queue.get(1)

print("consumed object from Q... size now",queue.qsize())

def writer(queue,loops):

# writer()函數(shù)只做一件事,就是一次往隊(duì)列中放入一個(gè)對象,等待一會,然后再做同樣的事

foriinrange(loops):

writeQ(queue)

sleep(1)

def reader(queue,loops):

# reader()函數(shù)只做一件事,就是一次從隊(duì)列中取出一個(gè)對象,等待一會,然后再做同樣的事

foriinrange(loops):

readQ(queue)

sleep(randint(2,5))

# 設(shè)置有多少個(gè)線程要被運(yùn)行

funcs=[writer,reader]

nfuncs=range(len(funcs))

def main():

nloops=randint(10,20)

q=Queue(32)

threads=[]

foriinnfuncs:

t=MyThread(funcs[i],(q,nloops),funcs[i].__name__)

threads.append(t)

foriinnfuncs:

threads[i].start()

foriinnfuncs:

threads[i].join()

printthreads[i].getResult()

print'all DONE'

if__name__=='__main__':

main()

FAQ

進(jìn)程與線程。線程與進(jìn)程的區(qū)別是什么?

進(jìn)程(有時(shí)被稱為重量級進(jìn)程)是程序的一次 執(zhí)行。每個(gè)進(jìn)程都有自己的地址空間,內(nèi)存,數(shù)據(jù)棧以及其它記錄其運(yùn)行軌跡的輔助數(shù)據(jù)。

線程(有時(shí)被稱為輕量級進(jìn)程)跟進(jìn)程有些相似,不同的是,所有的線程運(yùn)行在同一個(gè)進(jìn)程中, 共享相同的運(yùn)行環(huán)境。它們可以想像成是在主進(jìn)程或“主線程”中并行運(yùn)行的“迷你進(jìn)程”。

這篇文章很好的解釋了線程和進(jìn)程的區(qū)別,推薦閱讀: http://www.ruanyifeng.com/blo…

Python 的線程。在 Python 中,哪一種多線程的程序表現(xiàn)得更好,I/O 密集型的還是計(jì)算 密集型的?

由于GIL的緣故,對所有面向 I/O 的(會調(diào)用內(nèi)建的操作系統(tǒng) C 代碼的)程序來說,GIL 會在這個(gè) I/O 調(diào)用之 前被釋放,以允許其它的線程在這個(gè)線程等待 I/O 的時(shí)候運(yùn)行。如果某線程并未使用很多 I/O 操作, 它會在自己的時(shí)間片內(nèi)一直占用處理器(和 GIL)。也就是說,I/O 密集型的 Python 程序比計(jì)算密集 型的程序更能充分利用多線程環(huán)境的好處。

線程。你認(rèn)為,多CPU 的系統(tǒng)與一般的系統(tǒng)有什么大的不同?多線程的程序在這種系統(tǒng)上的表現(xiàn)會怎么樣?

Python的線程就是C語言的一個(gè)pthread,并通過操作系統(tǒng)調(diào)度算法進(jìn)行調(diào)度(例如linux是CFS)。為了讓各個(gè)線程能夠平均利用CPU時(shí)間,python會計(jì)算當(dāng)前已執(zhí)行的微代碼數(shù)量,達(dá)到一定閾值后就強(qiáng)制釋放GIL。而這時(shí)也會觸發(fā)一次操作系統(tǒng)的線程調(diào)度(當(dāng)然是否真正進(jìn)行上下文切換由操作系統(tǒng)自主決定)。

p

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,784評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,743評論 2 370

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

  • 引言&動機(jī) 考慮一下這個(gè)場景,我們有10000條數(shù)據(jù)需要處理,處理每條數(shù)據(jù)需要花費(fèi)1秒,但讀取數(shù)據(jù)只需要0.1秒,...
    chen_000閱讀 516評論 0 0
  • 我們前面提到了進(jìn)程是由若干線程組成的,一個(gè)進(jìn)程至少有一個(gè)線程。多線程優(yōu)點(diǎn): 在一個(gè)進(jìn)程中的多線程和主線程分享相同的...
    第八共同體閱讀 525評論 0 0
  • 1.進(jìn)程和線程 隊(duì)列:1、進(jìn)程之間的通信: q = multiprocessing.Queue()2、...
    一只寫程序的猿閱讀 1,118評論 0 17
  • 多線程模塊 threading 創(chuàng)建多線程的兩種方式:import threadingimport time 創(chuàng)建...
    錢塘閱讀 392評論 0 3
  • 線程狀態(tài)新建,就緒,運(yùn)行,阻塞,死亡。 線程同步多線程可以同時(shí)運(yùn)行多個(gè)任務(wù),線程需要共享數(shù)據(jù)的時(shí)候,可能出現(xiàn)數(shù)據(jù)不...
    KevinCool閱讀 814評論 0 0