python多線程thread
在使用多線程之前,我們首頁要理解什么是進(jìn)程和線程。
什么是進(jìn)程?
計(jì)算機(jī)程序只不過是磁盤中可執(zhí)行的,二進(jìn)制(或其它類型)的數(shù)據(jù)。它們只有在被讀取到內(nèi)存中,被操作系統(tǒng)調(diào)用的時(shí)候才開始它們的生命期。進(jìn)程(有時(shí)被稱為重量級(jí)進(jìn)程)是程序的一次執(zhí)行。每個(gè)進(jìn)程都有自己的地址空間,內(nèi)存,數(shù)據(jù)棧以及其它記錄其運(yùn)行軌跡的輔助數(shù)據(jù)。操作系統(tǒng)管理在其上運(yùn)行的所有進(jìn)程,并為這些進(jìn)程公平地分配時(shí)間。
什么是線程?
線程(有時(shí)被稱為輕量級(jí)進(jìn)程)跟進(jìn)程有些相似,不同的是,所有的線程運(yùn)行在同一個(gè)進(jìn)程中,共享相同的運(yùn)行環(huán)境。我們可以想像成是在主進(jìn)程或“主線程”中并行運(yùn)行的“迷你進(jìn)程”。
7.2.1、單線程
在單線程中順序執(zhí)行兩個(gè)循環(huán)。一定要一個(gè)循環(huán)結(jié)束后,另一個(gè)才能開始。總時(shí)間是各個(gè)循環(huán)運(yùn)行時(shí)間之和。
onetherad.py
from time import sleep, ctime def loop0(): print 'start loop 0 at:', ctime() sleep(4) print 'loop 0 done at:', ctime() def loop1(): print 'start loop 1 at:', ctime() sleep(2) print 'loop 1 done at:', ctime() def main(): print 'start:', ctime() loop0() loop1() print 'all end:', ctime() if __name__ == '__main__': main()
運(yùn)行結(jié)果:
start loop 0 at: Mon Dec 23 09:59:44 2013 loop 0 done at: Mon Dec 23 09:59:48 2013 start loop 1 at: Mon Dec 23 09:59:48 2013 loop 1 done at: Mon Dec 23 09:59:50 2013 all end: Mon Dec 23 09:59:50 2013
Python通過兩個(gè)標(biāo)準(zhǔn)庫thread和threading提供對(duì)線程的支持。thread提供了低級(jí)別的、原始的線程以及一個(gè)簡單的鎖。threading基于Java的線程模型設(shè)計(jì)。鎖(Lock)和條件變量(Condition)在Java中是對(duì)象的基本行為(每一個(gè)對(duì)象都自帶了鎖和條件變量),而在Python中則是獨(dú)立的對(duì)象。
7.2.1、thread模塊
mtsleep1.py
import thread from time import sleep, ctime loops = [4,2] def loop0(): print 'start loop 0 at:', ctime() sleep(4) print 'loop 0 done at:', ctime() def loop1(): print 'start loop 1 at:', ctime() sleep(2) print 'loop 1 done at:', ctime() def main(): print 'start:', ctime() thread.start_new_thread(loop0, ()) thread.start_new_thread(loop1, ()) sleep(6) print 'all end:', ctime() if __name__ == '__main__': main()
start_new_thread()要求一定要有前兩個(gè)參數(shù)。所以,就算我們想要運(yùn)行的函數(shù)不要參數(shù),我們也要傳一個(gè)空的元組。
這個(gè)程序的輸出與之前的輸出大不相同,之前是運(yùn)行了 6,7 秒,而現(xiàn)在則是 4 秒,是最長的循環(huán)的運(yùn)行時(shí)間與其它的代碼的時(shí)間總和。
運(yùn)行結(jié)果:
start: Mon Dec 23 10:05:09 2013 start loop 0 at: Mon Dec 23 10:05:09 2013 start loop 1 at: Mon Dec 23 10:05:09 2013 loop 1 done at: Mon Dec 23 10:05:11 2013 loop 0 done at: Mon Dec 23 10:05:13 2013 all end: Mon Dec 23 10:05:15 2013
睡眠 4 秒和 2 秒的代碼現(xiàn)在是并發(fā)執(zhí)行的。這樣,就使得總的運(yùn)行時(shí)間被縮短了。你可以看到,loop1 甚至在 loop0 前面就結(jié)束了。
程序的一大不同之處就是多了一個(gè)“sleep(6)”的函數(shù)調(diào)用。如果我們沒有讓主線程停下來,那主線程就會(huì)運(yùn)行下一條語句,顯示“all end”,然后就關(guān)閉運(yùn)行著 loop0()和 loop1()的兩個(gè)線程并退出了。我們使用 6 秒是因?yàn)槲覀円呀?jīng)知道,兩個(gè)線程(你知道,一個(gè)要 4 秒,一個(gè)要 2 秒)在主線程等待 6 秒后應(yīng)該已經(jīng)結(jié)束了。
你也許在想,應(yīng)該有什么好的管理線程的方法,而不是在主線程里做一個(gè)額外的延時(shí) 6 秒的操作。因?yàn)檫@樣一來,我們的總的運(yùn)行時(shí)間并不比單線程的版本來得少。而且,像這樣使用 sleep()函數(shù)做線程的同步操作是不可靠的。如果我們的循環(huán)的執(zhí)行時(shí)間不能事先確定的話,那怎么辦呢?這可能造成主線程過早或過晚退出。這就是鎖的用武之地了。
mtsleep2.py
#coding=utf-8 import thread from time import sleep, ctime loops = [4,2] def loop(nloop, nsec, lock): print 'start loop', nloop, 'at:', ctime() sleep(nsec) print 'loop', nloop, 'done at:', ctime() #解鎖 lock.release() def main(): print 'starting at:', ctime() locks =[] #以loops數(shù)組創(chuàng)建列表,并賦值給nloops nloops = range(len(loops)) for i in nloops: lock = thread.allocate_lock() #鎖定 lock.acquire() #追加到locks[]數(shù)組中 locks.append(lock) #執(zhí)行多線程 for i in nloops: thread.start_new_thread(loop,(i,loops[i],locks[i])) for i in nloops: while locks[i].locked(): pass print 'all end:', ctime() if __name__ == '__main__': main()
thread.allocate_lock()
返回一個(gè)新的鎖定對(duì)象。
acquire() /release()
一個(gè)原始的鎖有兩種狀態(tài),鎖定與解鎖,分別對(duì)應(yīng)acquire()和release() 方法。
range()
range()函數(shù)來創(chuàng)建列表包含算術(shù)級(jí)數(shù)。
range(len(loops))理解:
>>> aa= "hello" #長度計(jì)算 >>> len(aa) 5 #創(chuàng)建列表 >>> range(len(aa)) [0, 1, 2, 3, 4] #循環(huán)輸出列表元素 >>> for a in range(len(aa)): print a 0 1 2 3 4
我們先調(diào)用 thread.allocate_lock()函數(shù)創(chuàng)建一個(gè)鎖的列表,并分別調(diào)用各個(gè)鎖的 acquire()函數(shù)獲得鎖。獲得鎖表示“把鎖鎖上”。鎖上后,我們就把鎖放到鎖列表 locks 中。
下一個(gè)循環(huán)創(chuàng)建線程,每個(gè)線程都用各自的循環(huán)號(hào),睡眠時(shí)間和鎖為參數(shù)去調(diào)用 loop()函數(shù)。為什么我們不在創(chuàng)建鎖的循環(huán)里創(chuàng)建線程呢?有以下幾個(gè)原因:(1) 我們想到實(shí)現(xiàn)線程的同步,所以要讓“所有的馬同時(shí)沖出柵欄”。(2) 獲取鎖要花一些時(shí)間,如果你的線程退出得“太快”,可能會(huì)導(dǎo)致還沒有獲得鎖,線程就已經(jīng)結(jié)束了的情況。
在線程結(jié)束的時(shí)候,線程要自己去做解鎖操作。最后一個(gè)循環(huán)只是坐在那一直等(達(dá)到暫停主線程的目的),直到兩個(gè)鎖都被解鎖為止才繼續(xù)運(yùn)行。
mtsleep2.py運(yùn)行結(jié)果:
starting at: Mon Dec 23 20:57:26 2013 start loop start loop0 1at: at:Mon Dec 23 20:57:26 2013 Mon Dec 23 20:57:26 2013 loop 1 done at: Mon Dec 23 20:57:28 2013 loop 0 done at: Mon Dec 23 20:57:30 2013 all end: Mon Dec 23 20:57:30 2013
7.2.1、threading模塊
我們應(yīng)該避免使用thread模塊,原因是它不支持守護(hù)線程。當(dāng)主線程退出時(shí),所有的子線程不論它們是否還在工作,都會(huì)被強(qiáng)行退出。有時(shí)我們并不期望這種行為,這時(shí)就引入了守護(hù)線程的概念。threading模塊則支持守護(hù)線程。
mtsleep3.py
#coding=utf-8 import threading from time import sleep, ctime loops = [4,2] def loop(nloop, nsec): print 'start loop', nloop, 'at:', ctime() sleep(nsec) print 'loop', nloop, 'done at:', ctime() def main(): print 'starting at:', ctime() threads = [] nloops = range(len(loops)) #創(chuàng)建線程 for i in nloops: t = threading.Thread(target=loop,args=(i,loops[i])) threads.append(t) #開始線程 for i in nloops: threads[i].start() #等待所有結(jié)束線程 for i in nloops: threads[i].join() print 'all end:', ctime() if __name__ == '__main__': main()
運(yùn)行結(jié)果:
starting at: Mon Dec 23 22:58:55 2013 start loop 0 at: Mon Dec 23 22:58:55 2013 start loop 1 at: Mon Dec 23 22:58:55 2013 loop 1 done at: Mon Dec 23 22:58:57 2013 loop 0 done at: Mon Dec 23 22:58:59 2013 all end: Mon Dec 23 22:58:59 2013
start()
開始線程活動(dòng)
join()
等待線程終止
所有的線程都創(chuàng)建了之后,再一起調(diào)用 start()函數(shù)啟動(dòng),而不是創(chuàng)建一個(gè)啟動(dòng)一個(gè)。而且,不用再管理一堆鎖(分配鎖,獲得鎖,釋放鎖,檢查鎖的狀態(tài)等),只要簡單地對(duì)每個(gè)線程調(diào)用 join()函數(shù)就可以了。
join()會(huì)等到線程結(jié)束,或者在給了 timeout 參數(shù)的時(shí)候,等到超時(shí)為止。join()的另一個(gè)比較重要的方面是它可以完全不用調(diào)用。一旦線程啟動(dòng)后,就會(huì)一直運(yùn)行,直到線程的函數(shù)結(jié)束,退出為止。
使用可調(diào)用的類
mtsleep4.py
#coding=utf-8 import threading from time import sleep, ctime loops = [4,2] class ThreadFunc(object): def __init__(self,func,args,name=''): self.name=name self.func=func self.args=args def __call__(self): apply(self.func,self.args) def loop(nloop,nsec): print "seart loop",nloop,'at:',ctime() sleep(nsec) print 'loop',nloop,'done at:',ctime() def main(): print 'starting at:',ctime() threads=[] nloops = range(len(loops)) for i in nloops: #調(diào)用ThreadFunc實(shí)例化的對(duì)象,創(chuàng)建所有線程 t = threading.Thread( target=ThreadFunc(loop,(i,loops[i]),loop.__name__)) threads.append(t) #開始線程 for i in nloops: threads[i].start() #等待所有結(jié)束線程 for i in nloops: threads[i].join() print 'all end:', ctime() if __name__ == '__main__': main()
運(yùn)行結(jié)果:
starting at: Tue Dec 24 16:39:16 2013 seart loop 0 at: Tue Dec 24 16:39:16 2013 seart loop 1 at: Tue Dec 24 16:39:16 2013 loop 1 done at: Tue Dec 24 16:39:18 2013 loop 0 done at: Tue Dec 24 16:39:20 2013 all end: Tue Dec 24 16:39:20 2013
創(chuàng)建新線程的時(shí)候,Thread 對(duì)象會(huì)調(diào)用我們的ThreadFunc 對(duì)象,這時(shí)會(huì)用到一個(gè)特殊函數(shù)__call__()。由于我們已經(jīng)有了要用的參數(shù),所以就不用再傳到 Thread()的構(gòu)造函數(shù)中。由于我們有一個(gè)參數(shù)的元組,這時(shí)要在代碼中使用 apply()函數(shù)。
我們傳了一個(gè)可調(diào)用的類(的實(shí)例),而不是僅傳一個(gè)函數(shù)。
__init__()
方法在類的一個(gè)對(duì)象被建立時(shí)運(yùn)行。這個(gè)方法可以用來對(duì)你的對(duì)象做一些初始化。
apply()
apply(func [, args [, kwargs ]]) 函數(shù)用于當(dāng)函數(shù)參數(shù)已經(jīng)存在于一個(gè)元組或字典中時(shí),間接地調(diào)用函數(shù)。args是一個(gè)包含將要提供給函數(shù)的按位置傳遞的參數(shù)的元組。如果省略了args,任何參數(shù)都不會(huì)被傳遞,kwargs是一個(gè)包含關(guān)鍵字參數(shù)的字典。
apply() 用法:
#不帶參數(shù)的方法 >>> def say(): print 'say in' >>> apply(say) say in #函數(shù)只帶元組的參數(shù) >>> def say(a,b): print a,b >>> apply(say,('hello','蟲師')) hello 蟲師 #函數(shù)帶關(guān)鍵字參數(shù) >>> def say(a=1,b=2): print a,b >>> def haha(**kw): apply(say,(),kw) >>> haha(a='a',b='b') a b
posted on 2014-02-27 14:39 順其自然EVO 閱讀(229) 評(píng)論(0) 編輯 收藏 所屬分類: python