進(jìn)程與線程的區(qū)別
現(xiàn)在,多核CPU已經(jīng)非常普及了陈症,但是蔼水,即使過(guò)去的單核CPU,也可以執(zhí)行多任務(wù)爬凑。由于CPU執(zhí)行代碼都是順序執(zhí)行的徙缴,那么试伙,單核CPU是怎么執(zhí)行多任務(wù)的呢嘁信?
答案就是操作系統(tǒng)輪流讓各個(gè)任務(wù)交替執(zhí)行,任務(wù)1執(zhí)行0.01秒疏叨,切換到任務(wù)2潘靖,任務(wù)2執(zhí)行0.01秒,再切換到任務(wù)3蚤蔓,執(zhí)行0.01秒……這樣反復(fù)執(zhí)行下去卦溢。表面上看,每個(gè)任務(wù)都是交替執(zhí)行的秀又,但是单寂,由于CPU的執(zhí)行速度實(shí)在是太快了,我們感覺(jué)就像所有任務(wù)都在同時(shí)執(zhí)行一樣吐辙。
真正的并行執(zhí)行多任務(wù)只能在多核CPU上實(shí)現(xiàn)宣决,但是,由于任務(wù)數(shù)量遠(yuǎn)遠(yuǎn)多于CPU的核心數(shù)量昏苏,所以尊沸,操作系統(tǒng)也會(huì)自動(dòng)把很多任務(wù)輪流調(diào)度到每個(gè)核心上執(zhí)行。
對(duì)于操作系統(tǒng)來(lái)說(shuō)贤惯,一個(gè)任務(wù)就是一個(gè)進(jìn)程(Process)洼专,比如打開(kāi)一個(gè)瀏覽器就是啟動(dòng)一個(gè)瀏覽器進(jìn)程,打開(kāi)一個(gè)記事本就啟動(dòng)了一個(gè)記事本進(jìn)程孵构,打開(kāi)兩個(gè)記事本就啟動(dòng)了兩個(gè)記事本進(jìn)程屁商,打開(kāi)一個(gè)Word就啟動(dòng)了一個(gè)Word進(jìn)程。
有些進(jìn)程還不止同時(shí)干一件事颈墅,比如Word蜡镶,它可以同時(shí)進(jìn)行打字、拼寫檢查精盅、打印等事情帽哑。在一個(gè)進(jìn)程內(nèi)部,要同時(shí)干多件事叹俏,就需要同時(shí)運(yùn)行多個(gè)“子任務(wù)”妻枕,我們把進(jìn)程內(nèi)的這些“子任務(wù)”稱為線程(Thread)。
由于每個(gè)進(jìn)程至少要干一件事,所以屡谐,一個(gè)進(jìn)程至少有一個(gè)線程述么。當(dāng)然,像Word這種復(fù)雜的進(jìn)程可以有多個(gè)線程愕掏,多個(gè)線程可以同時(shí)執(zhí)行度秘,多線程的執(zhí)行方式和多進(jìn)程是一樣的,也是由操作系統(tǒng)在多個(gè)線程之間快速切換饵撑,讓每個(gè)線程都短暫地交替運(yùn)行剑梳,看起來(lái)就像同時(shí)執(zhí)行一樣。當(dāng)然滑潘,真正地同時(shí)執(zhí)行多線程需要多核CPU才可能實(shí)現(xiàn)垢乙。
- 定義方面:進(jìn)程是程序在某個(gè)數(shù)據(jù)集合上的一次運(yùn)行活動(dòng);線程是進(jìn)程中的一個(gè)執(zhí)行路徑语卤。
- 角色方面:在支持線程機(jī)制的系統(tǒng)中追逮,進(jìn)程是系統(tǒng)資源分配的單位,線程是系統(tǒng)調(diào)度的單位粹舵。
- 資源共享方面:進(jìn)程之間不能共享資源钮孵,而線程共享所在進(jìn)程的地址空間和其它資源。同時(shí)線程還有自己的棧和棧指針眼滤,程序計(jì)數(shù)器等寄存器巴席。
- 獨(dú)立性方面:進(jìn)程有自己獨(dú)立的地址空間,而線程沒(méi)有柠偶,線程必須依賴于進(jìn)程而存在情妖,線程之間共享地址空間。
進(jìn)程和線程的最主要區(qū)別在于它們的系統(tǒng)資源管理方式不同诱担。進(jìn)程有獨(dú)立的地址空間毡证,一個(gè)進(jìn)程崩潰后,在保護(hù)模式下不會(huì)對(duì)其它進(jìn)程產(chǎn)生影響蔫仙。而線程只是一個(gè)進(jìn)程中的不同執(zhí)行路徑料睛,線程有自己的堆棧和局部變量,但線程之間沒(méi)有單獨(dú)的地址空間摇邦,一個(gè)線程死掉就等于整個(gè)進(jìn)程死掉恤煞,所以多進(jìn)程的程序要比多線程的程序健壯,但在進(jìn)程切換時(shí)施籍,耗費(fèi)資源較大居扒,效率要差一些。
python多進(jìn)程的實(shí)現(xiàn)
如果你打算編寫多進(jìn)程的服務(wù)程序丑慎,Unix/Linux無(wú)疑是正確的選擇喜喂,在Unix/Linux上實(shí)現(xiàn)多進(jìn)程可以使用fork模塊瓤摧。但Windows沒(méi)有fork調(diào)用,難道在Windows上無(wú)法用Python編寫多進(jìn)程的程序玉吁?
由于Python是跨平臺(tái)的照弥,自然也應(yīng)該提供一個(gè)跨平臺(tái)的多進(jìn)程支持。multiprocessing模塊就是跨平臺(tái)版本的多進(jìn)程模塊进副。
window系統(tǒng)下这揣,需要注意的是要想啟動(dòng)一個(gè)子進(jìn)程,必須加上那句if __name__ == "main"影斑,進(jìn)程相關(guān)的要寫在這句下面给赞。
1、Process類
構(gòu)造方法:
Process([group [, target [, name [, args [, kwargs]]]]])
group: 線程組鸥昏,目前還沒(méi)有實(shí)現(xiàn)塞俱,庫(kù)引用中提示必須是None姐帚;
target: 要執(zhí)行的方法吏垮;
name: 進(jìn)程名,可為空罐旗;
args/kwargs: 要傳入方法的參數(shù)膳汪。
實(shí)例方法:
is_alive():返回進(jìn)程是否在運(yùn)行。
join([timeout]):join()代表啟動(dòng)多進(jìn)程九秀,但是阻塞并發(fā)運(yùn)行遗嗽,一個(gè)進(jìn)程執(zhí)行結(jié)束后再執(zhí)行第二個(gè)進(jìn)程」难眩可以給其設(shè)置一個(gè)timeout值比如join(5)代表5秒后無(wú)論當(dāng)前進(jìn)程是否結(jié)果都繼續(xù)并發(fā)執(zhí)行第二個(gè)進(jìn)程痹换。
start():進(jìn)程準(zhǔn)備就緒,等待CPU調(diào)度
run():strat()調(diào)用run方法都弹,如果實(shí)例進(jìn)程時(shí)未制定傳入target娇豫,這start執(zhí)行默認(rèn)run()方法。
terminate():不管任務(wù)是否完成畅厢,立即停止工作進(jìn)程冯痢。
屬性:
authkey
daemon:和線程的setDeamon功能一樣
exitcode(進(jìn)程在運(yùn)行時(shí)為None、如果為–N框杜,表示被信號(hào)N結(jié)束)
name:進(jìn)程名字浦楣。
pid:進(jìn)程號(hào)。
使用Process類創(chuàng)建多個(gè)進(jìn)程試一下:
from multiprocessing import Process
import threading
import time
def foo(i):
print 'say hi', i
if __name__ == '__main__':
for i in range(10):
p = Process(target=foo, args=(i,))
p.start()
# p.join()
運(yùn)行結(jié)果咪辱,可以看出多個(gè)進(jìn)程隨機(jī)順序執(zhí)行振劳。
say hi 4
say hi 3
say hi 5
say hi 2
say hi 1
say hi 6
say hi 0
say hi 7
say hi 8
say hi 9
Process finished with exit code 0
對(duì)比一下未注釋p.join()時(shí)的執(zhí)行結(jié)果,很明顯看出區(qū)別
say hi 0
say hi 1
say hi 2
say hi 3
say hi 4
say hi 5
say hi 6
say hi 7
say hi 8
say hi 9
Process finished with exit code 0
2油狂、Pool類
進(jìn)程池內(nèi)部維護(hù)一個(gè)進(jìn)程序列历恐,當(dāng)使用時(shí)庐杨,則去進(jìn)程池中獲取一個(gè)進(jìn)程,如果進(jìn)程池序列中沒(méi)有可供使用的進(jìn)進(jìn)程夹供,那么程序就會(huì)等待灵份,直到進(jìn)程池中有可用進(jìn)程為止。進(jìn)程池設(shè)置最好等于CPU核心數(shù)量
構(gòu)造方法:
Pool([processes[, initializer[, initargs[, maxtasksperchild[, context]]]]])
processes:使用的工作進(jìn)程的數(shù)量哮洽,如果processes是None那么使用 os.cpu_count()返回的數(shù)量填渠,即默認(rèn)為cpu的核心數(shù)。
initializer:如果initializer是None鸟辅,那么每一個(gè)工作進(jìn)程在開(kāi)始的時(shí)候會(huì)調(diào)用initializer(*initargs)氛什。
maxtasksperchild:工作進(jìn)程退出之前可以完成的任務(wù)數(shù),完成后用一個(gè)新的工作進(jìn)程來(lái)替代原進(jìn)程匪凉,來(lái)讓閑置的資源被釋放枪眉。maxtasksperchild默認(rèn)是None,意味著只要Pool存在工作進(jìn)程就會(huì)一直存活再层。
context: 用在制定工作進(jìn)程啟動(dòng)時(shí)的上下文贸铜,一般使用 multiprocessing.Pool() 或者一個(gè)context對(duì)象的Pool()方法來(lái)創(chuàng)建一個(gè)池,兩種方法都適當(dāng)?shù)脑O(shè)置了context
實(shí)例方法:
apply(func[, args[, kwds]]):同步進(jìn)程池
apply_async(func[, args[, kwds[, callback[, error_callback]]]]) :異步進(jìn)程池
close() : 關(guān)閉進(jìn)程池聂受,阻止更多的任務(wù)提交到pool蒿秦,待任務(wù)完成后,工作進(jìn)程會(huì)退出蛋济。
terminate() : 結(jié)束工作進(jìn)程棍鳖,不在處理未完成的任務(wù)
join() : wait工作進(jìn)程的退出,在調(diào)用join()前碗旅,必須調(diào)用close() or terminate()渡处。這樣是因?yàn)楸唤K止的進(jìn)程需要被父進(jìn)程調(diào)用wait(join等價(jià)與wait),否則進(jìn)程會(huì)成為僵尸進(jìn)程祟辟。pool.join()必須使用在close() or terminate()之后医瘫。
例子一(異步進(jìn)程池):
# coding:utf-8
from multiprocessing import Pool
import time
def Foo(i):
time.sleep(2)
return i + 100
def Bar(arg):
print arg
if __name__ == '__main__':
t_start=time.time()
pool = Pool(5) #池內(nèi)5個(gè)進(jìn)程
for i in range(10):
pool.apply_async(func=Foo, args=(i,), callback=Bar)#異步,維持執(zhí)行的進(jìn)程總數(shù)為processes川尖,當(dāng)一個(gè)進(jìn)程執(zhí)行完畢后會(huì)添加新的進(jìn)程進(jìn)去
pool.close()
pool.join() # 進(jìn)程池中進(jìn)程執(zhí)行完畢后再關(guān)閉登下,如果注釋,那么程序直接關(guān)閉叮喳。
pool.terminate()
t_end=time.time()
t=t_end-t_start
print 'the program time is :%s' %t
運(yùn)行結(jié)果如下被芳,結(jié)果打印時(shí)分兩次打印,每次打印5個(gè)數(shù)馍悟,即池內(nèi)5個(gè)進(jìn)程是異步運(yùn)行的畔濒。
101
100
102
103
104
106
105
107
108
109
the program time is :4.22099995613
Process finished with exit code 0
例子二(同步進(jìn)程池):
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from multiprocessing import Process, Pool
import time
def Foo(i):
time.sleep(1)
print i + 100
if __name__ == '__main__':
t_start=time.time()
pool = Pool(5)
for i in range(10):
pool.apply(Foo, (i,)) #同步進(jìn)程
pool.close()
pool.join() # 進(jìn)程池中進(jìn)程執(zhí)行完畢后再關(guān)閉,如果注釋锣咒,那么程序直接關(guān)閉侵状。
t_end=time.time()
t=t_end-t_start
print 'the program time is :%s' %t
運(yùn)行結(jié)果赞弥,結(jié)果打印時(shí)一個(gè)一個(gè)打印,即池內(nèi)進(jìn)程是串行運(yùn)行的趣兄。
100
101
102
103
104
105
106
107
108
109
the program time is :4.334000110626221
Process finished with exit code 0
如果將pool.join()注釋掉绽左,那么進(jìn)程未執(zhí)行完就會(huì)被關(guān)閉,運(yùn)行結(jié)果就會(huì)成為下面這個(gè)樣子艇潭。
the program time is :0.7160000801086426
3拼窥、子進(jìn)程
很多時(shí)候,子進(jìn)程并不是自身蹋凝,而是一個(gè)外部進(jìn)程鲁纠。我們創(chuàng)建了子進(jìn)程后,還需要控制子進(jìn)程的輸入和輸出鳍寂。
subprocess模塊可以讓我們非常方便地啟動(dòng)一個(gè)子進(jìn)程改含,然后控制其輸入和輸出。
下面的例子演示了如何在Python代碼中運(yùn)行命令nslookup www.python.org迄汛,這和命令行直接運(yùn)行的效果是一樣的:
import subprocess
print('$ nslookup www.python.org')
r = subprocess.call(['nslookup', 'www.python.org'])
print('Exit code:', r)
運(yùn)行結(jié)果
$ nslookup www.python.org
Server: 192.168.19.4
Address: 192.168.19.4#53
Non-authoritative answer:
www.python.org canonical name = python.map.fastly.net.
Name: python.map.fastly.net
Address: 199.27.79.223
Exit code: 0
如果子進(jìn)程還需要輸入捍壤,則可以通過(guò)communicate()方法輸入:
import subprocess
print('$ nslookup')
p = subprocess.Popen(['nslookup'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = p.communicate(b'set q=mx\npython.org\nexit\n')
print(output.decode('utf-8'))
print('Exit code:', p.returncode)
上面的代碼相當(dāng)于在命令行執(zhí)行命令nslookup,然后手動(dòng)輸入:
set q=mx
python.org
exit
運(yùn)行結(jié)果如下:
$ nslookup
Server: 192.168.19.4
Address: 192.168.19.4#53
Non-authoritative answer:
python.org mail exchanger = 50 mail.python.org.
Authoritative answers can be found from:
mail.python.org internet address = 82.94.164.166
mail.python.org has AAAA address 2001:888:2000:d::a6
Exit code: 0
可以參考http://www.xuebuyuan.com/2118731.html
4隔心、進(jìn)程間通信
Process之間肯定是需要通信的白群,操作系統(tǒng)提供了很多機(jī)制來(lái)實(shí)現(xiàn)進(jìn)程間的通信。Python的multiprocessing模塊包裝了底層的機(jī)制硬霍,提供了Queue、Pipes等多種方式來(lái)交換數(shù)據(jù)笼裳。
我們以Queue為例唯卖,在父進(jìn)程中創(chuàng)建兩個(gè)子進(jìn)程,一個(gè)往Queue里寫數(shù)據(jù)躬柬,一個(gè)從Queue里讀數(shù)據(jù):
from multiprocessing import Process, Queue
import os, time, random
# 寫數(shù)據(jù)進(jìn)程執(zhí)行的代碼:
def write(q):
print('Process to write: %s' % os.getpid())
for value in ['A', 'B', 'C']:
print('Put %s to queue...' % value)
q.put(value)
time.sleep(random.random())
# 讀數(shù)據(jù)進(jìn)程執(zhí)行的代碼:
def read(q):
print('Process to read: %s' % os.getpid())
while True:
value = q.get(True)
print('Get %s from queue.' % value)
if __name__=='__main__':
# 父進(jìn)程創(chuàng)建Queue拜轨,并傳給各個(gè)子進(jìn)程:
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
# 啟動(dòng)子進(jìn)程pw,寫入:
pw.start()
# 啟動(dòng)子進(jìn)程pr允青,讀取:
pr.start()
# 等待pw結(jié)束:
pw.join()
# pr進(jìn)程里是死循環(huán)橄碾,無(wú)法等待其結(jié)束,只能強(qiáng)行終止:
pr.terminate()
運(yùn)行結(jié)果
Process to write: 50563
Put A to queue...
Process to read: 50564
Get A from queue.
Put B to queue...
Get B from queue.
Put C to queue...
Get C from queue.
python多線程的實(shí)現(xiàn)
Python的標(biāo)準(zhǔn)庫(kù)提供了兩個(gè)模塊:_thread和threading颠锉,_thread是低級(jí)模塊法牲,threading是高級(jí)模塊,對(duì)_thread進(jìn)行了封裝琼掠。絕大多數(shù)情況下拒垃,我們只需要使用threading這個(gè)高級(jí)模塊。
threading模塊提供的類:
Thread, Lock, Rlock, Condition, [Bounded]Semaphore, Event, Timer, local瓷蛙。
threading 模塊提供的常用方法:
threading.currentThread(): 返回當(dāng)前的線程變量悼瓮。
threading.enumerate(): 返回一個(gè)包含正在運(yùn)行的線程的list戈毒。正在運(yùn)行指線程啟動(dòng)后、結(jié)束前横堡,不包括啟動(dòng)前和終止后的線程埋市。
threading.activeCount(): 返回正在運(yùn)行的線程數(shù)量,與len(threading.enumerate())有相同的結(jié)果命贴。
threading 模塊提供的常量:
threading.TIMEOUT_MAX 設(shè)置threading全局超時(shí)時(shí)間恐疲。
1、Thread類
構(gòu)造方法:
Thread(group=None, target=None, name=None, args=(), kwargs={})
group: 線程組套么,目前還沒(méi)有實(shí)現(xiàn)培己,庫(kù)引用中提示必須是None;
target: 要執(zhí)行的方法胚泌;
name: 線程名省咨;
args/kwargs: 要傳入方法的參數(shù)。
實(shí)例方法:
isAlive(): 返回線程是否在運(yùn)行玷室。正在運(yùn)行指啟動(dòng)后零蓉、終止前。
get/setName(name): 獲取/設(shè)置線程名穷缤。
start(): 線程準(zhǔn)備就緒敌蜂,等待CPU調(diào)度
is/setDaemon(bool): 獲取/設(shè)置是后臺(tái)線程(默認(rèn)前臺(tái)線程(False))。(在start之前設(shè)置)
如果是后臺(tái)線程津肛,主線程執(zhí)行過(guò)程中章喉,后臺(tái)線程也在進(jìn)行,主線程執(zhí)行完畢后身坐,后臺(tái)線程不論成功與否秸脱,主線程和后臺(tái)線程均停止
如果是前臺(tái)線程,主線程執(zhí)行過(guò)程中部蛇,前臺(tái)線程也在進(jìn)行摊唇,主線程執(zhí)行完畢后,等待前臺(tái)線程也執(zhí)行完成后涯鲁,程序停止
start(): 啟動(dòng)線程巷查。
join([timeout]): 阻塞當(dāng)前上下文環(huán)境的線程,直到調(diào)用此方法的線程終止或到達(dá)指定的timeout(可選參數(shù))抹腿。
使用例子一(未設(shè)置setDeamon)
# coding:utf-8
import threading
import time
def action(arg):
time.sleep(1)
print 'sub thread start!the thread name is:%s\r' % threading.currentThread().getName()
print 'the arg is:%s\r' %arg
time.sleep(1)
for i in xrange(4):
t =threading.Thread(target=action,args=(i,))
# t.setDaemon(True)#設(shè)置線程為后臺(tái)線程
t.start()
print 'main_thread end!'
運(yùn)行結(jié)果岛请,從結(jié)果可以看出主線程執(zhí)行過(guò)程中,前臺(tái)線程也在進(jìn)行幢踏,主線程執(zhí)行完畢后髓需,等待前臺(tái)線程也執(zhí)行完成后,程序停止房蝉。
main_thread end!
sub thread start!the thread name is:Thread-1
the arg is:0
sub thread start!the thread name is:Thread-3
the arg is:2
the arg is:1
sub thread start!the thread name is:Thread-4
the arg is:3
Process finished with exit code 0
使用例子二(設(shè)置setDeamon)僚匆,當(dāng) t.setDaemon(True)未被注釋時(shí)的運(yùn)行結(jié)果如下微渠;
main_thread end!
Process finished with exit code 0
運(yùn)行結(jié)果,從結(jié)果可以看出當(dāng)主線程執(zhí)行完畢后咧擂,后臺(tái)線程不論成功與否逞盆,主線程和后臺(tái)線程均停止,所以沒(méi)打印后臺(tái)線程的信息松申。
join用法
#coding=utf-8
import threading
import time
def action(arg):
time.sleep(1)
print ('sub thread start!the thread name is:%s ' % threading.currentThread().getName())
print ('the arg is:%s ' %arg)
time.sleep(1)
thread_list = [] #線程存放列表
for i in range(4):
t =threading.Thread(target=action,args=(i,))
t.setDaemon(True)
thread_list.append(t)
for t in thread_list:
t.start()
for t in thread_list:
t.join()
print (type(thread_list[0]))
運(yùn)行結(jié)果云芦,驗(yàn)證了 join()阻塞當(dāng)前上下文環(huán)境的線程,直到調(diào)用此方法的線程終止或到達(dá)指定的timeout贸桶,即使設(shè)置了setDeamon(True)主線程依然要等待子線程結(jié)束舅逸。
sub thread start!the thread name is:Thread-1
the arg is:0
sub thread start!the thread name is:Thread-3
the arg is:2
sub thread start!the thread name is:Thread-4
the arg is:3
sub thread start!the thread name is:Thread-2
the arg is:1
<class 'threading.Thread'>
Process finished with exit code 0
注意t.join()不要和t.start()一同寫在循環(huán)里,否則會(huì)出現(xiàn)每個(gè)線程都被上一個(gè)線程的join阻塞皇筛,線程只能挨個(gè)執(zhí)行琉历,使得“多線程”失去了多線程意義。
2水醋、Lock旗笔、Rlock類
多線程和多進(jìn)程最大的不同在于,多進(jìn)程中拄踪,同一個(gè)變量蝇恶,各自有一份拷貝存在于每個(gè)進(jìn)程中,互不影響撮弧,而多線程中耀盗,所有變量都由所有線程共享,所以叛拷,任何一個(gè)變量都可以被任何一個(gè)線程修改,因此岂却,線程之間共享數(shù)據(jù)最大的危險(xiǎn)在于多個(gè)線程同時(shí)改一個(gè)變量,把內(nèi)容給改亂了躏哩。
為了多個(gè)線程同時(shí)操作一個(gè)內(nèi)存中的資源時(shí)不產(chǎn)生混亂,我們使用鎖扫尺。
Lock(指令鎖)是可用的最低級(jí)的同步指令筋栋。Lock處于鎖定狀態(tài)時(shí),不被特定的線程擁有弊攘。Lock包含兩種狀態(tài)——鎖定和非鎖定,以及兩個(gè)基本的方法襟交。
可以認(rèn)為L(zhǎng)ock有一個(gè)鎖定池捣域,當(dāng)線程請(qǐng)求鎖定時(shí),將線程至于池中焕梅,直到獲得鎖定后出池。池中的線程處于狀態(tài)圖中的同步阻塞狀態(tài)斜棚。
RLock(可重入鎖)是一個(gè)可以被同一個(gè)線程請(qǐng)求多次的同步指令蜗字。RLock使用了“擁有的線程”和“遞歸等級(jí)”的概念,處于鎖定狀態(tài)時(shí)粗梭,RLock被某個(gè)線程擁有级零。擁有RLock的線程可以再次調(diào)用acquire(),釋放鎖時(shí)需要調(diào)用release()相同次數(shù)奏纪。
可以認(rèn)為RLock包含一個(gè)鎖定池和一個(gè)初始值為0的計(jì)數(shù)器序调,每次成功調(diào)用 acquire()/release(),計(jì)數(shù)器將+1/-1发绢,為0時(shí)鎖處于未鎖定狀態(tài)。
簡(jiǎn)言之:Lock屬于全局经柴,Rlock屬于線程墩朦。
構(gòu)造方法:
Lock(),Rlock(),推薦使用Rlock()
**實(shí)例方法: **
acquire([timeout]): 嘗試獲得鎖定牛哺。使線程進(jìn)入同步阻塞狀態(tài)陋气。
release(): 釋放鎖。使用前線程必須已獲得鎖定恩伺,否則將拋出異常椰拒。
使用例子一(未加鎖)
import time, threading
# 假定這是你的銀行存款:
balance = 0
def change_it(n):
# 先存后取,結(jié)果應(yīng)該為0:
global balance
balance = balance + n
balance = balance - n
def run_thread(n):
for i in range(100000):
change_it(n)
t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)
這段代碼先存后取褒脯,結(jié)果應(yīng)該恒為0缆毁,但是當(dāng)操作系統(tǒng)以下面的順序執(zhí)行t1、t2就不一定了颁督。
初始值 balance = 0
t1: x1 = balance + 5 # x1 = 0 + 5 = 5
t2: x2 = balance + 8 # x2 = 0 + 8 = 8
t2: balance = x2 # balance = 8
t1: balance = x1 # balance = 5
t1: x1 = balance - 5 # x1 = 5 - 5 = 0
t1: balance = x1 # balance = 0
t2: x2 = balance - 8 # x2 = 0 - 8 = -8
t2: balance = x2 # balance = -8
結(jié)果 balance = -8
使用例子二(加鎖)
#coding=utf-8
import time, threading
# 假定這是你的銀行存款:
balance = 0
lock = threading.Lock()
def run_thread(n):
for i in range(100000):
# 先要獲取鎖:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要釋放鎖:
lock.release()
def change_it(n):
# 先存后取沉御,結(jié)果應(yīng)該為0:
global balance
balance = balance + n
balance = balance - n
# def run_thread(n):
# for i in range(100000):
# change_it(n)
t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t3 = threading.Thread(target=run_thread, args=(7,))
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
print(balance)
在有鎖的情形下昭灵,即使線程再多,也能保證資源不亂试疙。
使用例子三(Lock與Rlock)
#coding:utf-8
import threading
lock = threading.Lock() #Lock對(duì)象
lock.acquire()
lock.acquire() #鎖未釋放抠蚣,無(wú)法再次獲取,一直等待從而產(chǎn)生死鎖缓屠。
lock.release()
lock.release()
print lock.acquire()
import threading
rLock = threading.RLock() #RLock對(duì)象
rLock.acquire()
rLock.acquire() #在同一線程內(nèi)护侮,程序不會(huì)堵塞储耐。
rLock.release()
rLock.release()
print (rLock.acquire())
上面兩段代碼執(zhí)行結(jié)果,前者產(chǎn)生了死鎖长赞,后者打印結(jié)果為True。
3脯颜、Condition類
Condition(條件變量)通常與一個(gè)鎖關(guān)聯(lián)贩据。需要在多個(gè)Contidion中共享一個(gè)鎖時(shí),可以傳遞一個(gè)Lock/RLock實(shí)例給構(gòu)造方法矾芙,否則它將自己生成一個(gè)RLock實(shí)例近上。
可以認(rèn)為,除了Lock帶有的鎖定池外葱绒,Condition還包含一個(gè)等待池斗锭,池中的線程處于等待阻塞狀態(tài),直到另一個(gè)線程調(diào)用notify()/notifyAll()通知骚秦;得到通知后線程進(jìn)入鎖定池等待鎖定璧微。
**構(gòu)造方法: **
Condition([lock/rlock])
**實(shí)例方法: **
acquire([timeout])/release(): 調(diào)用關(guān)聯(lián)的鎖的相應(yīng)方法。
wait([timeout]): 調(diào)用這個(gè)方法將使線程進(jìn)入Condition的等待池等待通知胞得,并釋放鎖屹电。使用前線程必須已獲得鎖定危号,否則將拋出異常。
notify(): 調(diào)用這個(gè)方法將從等待池挑選一個(gè)線程并通知外莲,收到通知的線程將自動(dòng)調(diào)用acquire()嘗試獲得鎖定(進(jìn)入鎖定池);其他線程仍然在等待池中磨确。調(diào)用這個(gè)方法不會(huì)釋放鎖定乏奥。使用前線程必須已獲得鎖定,否則將拋出異常邓了。
notifyAll(): 調(diào)用這個(gè)方法將通知等待池中所有的線程驶悟,這些線程都將進(jìn)入鎖定池嘗試獲得鎖定。調(diào)用這個(gè)方法不會(huì)釋放鎖定硫豆。使用前線程必須已獲得鎖定笼呆,否則將拋出異常。
4汗茄、Event類
Event(事件)是最簡(jiǎn)單的線程通信機(jī)制之一:一個(gè)線程通知事件铭若,其他線程等待事件叼屠。Event內(nèi)置了一個(gè)初始為False的標(biāo)志,當(dāng)調(diào)用set()時(shí)設(shè)為True镜雨,調(diào)用clear()時(shí)重置為 False荚坞。wait()將阻塞線程至等待阻塞狀態(tài)。
Event其實(shí)就是一個(gè)簡(jiǎn)化版的 Condition各淀。Event沒(méi)有鎖诡挂,無(wú)法使線程進(jìn)入同步阻塞狀態(tài)疗我。
**構(gòu)造方法: **
Event()
**實(shí)例方法: **
isSet(): 當(dāng)內(nèi)置標(biāo)志為True時(shí)返回True。
set(): 將標(biāo)志設(shè)為True旧找,并通知所有處于等待阻塞狀態(tài)的線程恢復(fù)運(yùn)行狀態(tài)。
clear(): 將標(biāo)志設(shè)為False鞭缭。
wait([timeout]): 如果標(biāo)志為True將立即返回魏颓,否則阻塞線程至等待阻塞狀態(tài)甸饱,等待其他線程調(diào)用set()。
5偷遗、timer類
Timer(定時(shí)器)是Thread的派生類驼壶,用于在指定時(shí)間后調(diào)用一個(gè)方法。
**構(gòu)造方法: **
Timer(interval, function, args=[], kwargs={})
interval: 指定的時(shí)間
function: 要執(zhí)行的方法
args/kwargs: 方法的參數(shù)
**實(shí)例方法: **
Timer從Thread派生泵喘,沒(méi)有增加實(shí)例方法般妙。
# encoding: UTF-8
import threading
def func():
print 'hello timer!'
timer = threading.Timer(5, func) #線程延遲5秒后執(zhí)行
timer.start()
6股冗、local類
local是一個(gè)小寫字母開(kāi)頭的類,用于管理 thread-local(線程局部的)數(shù)據(jù)烹棉。對(duì)于同一個(gè)local怯疤,線程無(wú)法訪問(wèn)其他線程設(shè)置的屬性;線程設(shè)置的屬性不會(huì)被其他線程設(shè)置的同名屬性替換伏社。
可以把local看成是一個(gè)“線程-屬性字典”的字典,local封裝了從自身使用線程作為 key檢索對(duì)應(yīng)的屬性字典速妖、再使用屬性名作為key檢索屬性值的細(xì)節(jié)聪黎。
# encoding: UTF-8
import threading
local = threading.local()
local.tname = 'main'
def func():
local.tname = 'notmain'
print local.tname
t1 = threading.Thread(target=func)
t1.start()
t1.join()
print local.tname
運(yùn)行結(jié)果
notmain
main
7稿饰、其它
Python的線程是真正的Posix Thread,而不是模擬出來(lái)的線程旅择。按理說(shuō)侣姆,多于多核cpu,可以同時(shí)執(zhí)行多個(gè)線程汇歹。對(duì)于N核CPU偿凭,啟動(dòng)N個(gè)死循環(huán)線程應(yīng)該可以將cpu利用率拔高到100%。
實(shí)際上卻不是痰哨,因?yàn)镻ython的線程雖然是真正的線程斤斧,但解釋器執(zhí)行代碼時(shí)霎烙,有一個(gè)GIL鎖:Global Interpreter Lock,任何Python線程執(zhí)行前游昼,必須先獲得GIL鎖尝蠕,然后,每執(zhí)行100條字節(jié)碼廊佩,解釋器就自動(dòng)釋放GIL鎖,讓別的線程有機(jī)會(huì)執(zhí)行顽铸。這個(gè)GIL全局鎖實(shí)際上把所有線程的執(zhí)行代碼都給上了鎖鸯绿,所以瓶蝴,多線程在Python中只能交替執(zhí)行租幕,即使100個(gè)線程跑在100核CPU上,也只能用到1個(gè)核男窟。
GIL是Python解釋器設(shè)計(jì)的歷史遺留問(wèn)題贾富,通常我們用的解釋器是官方實(shí)現(xiàn)的CPython颤枪,要真正利用多核,除非重寫一個(gè)不帶GIL的解釋器扇住。
所以盗胀,在Python中,可以使用多線程女阀,但不要指望能有效利用多核屑迂。如果一定要通過(guò)多線程利用多核屈糊,那只能通過(guò)C擴(kuò)展來(lái)實(shí)現(xiàn),不過(guò)這樣就失去了Python簡(jiǎn)單易用的特點(diǎn)夫晌。
不過(guò),也不用過(guò)于擔(dān)心所袁,Python雖然不能利用多線程實(shí)現(xiàn)多核任務(wù)凶掰,但可以通過(guò)多進(jìn)程實(shí)現(xiàn)多核任務(wù)。多個(gè)Python進(jìn)程有各自獨(dú)立的GIL鎖前翎,互不影響港华。
參考:
http://www.cnblogs.com/tkqasn/p/5700281.html http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143192823818768cd506abbc94eb5916192364506fa5d000