咱們先來了解什麼是進程?html
程序並不能單獨運行,只有將程序裝載到內存中,系統爲它分配資源才能運行,而這種執行的程序就稱之爲進程。程序和進程的區別就在於:程序是指令的集合,它是進程運行的靜態描述文本;進程是程序的一次執行活動,屬於動態概念。python
在多道編程中,咱們容許多個程序同時加載到內存中,在操做系統的調度下,能夠實現併發地執行。這是這樣的設計,大大提升了CPU的利用率。進程的出現讓每一個用戶感受到本身獨享CPU,所以,進程就是爲了在CPU上實現多道編程而提出的。編程
進程提供了多道編程,充分發揮了計算機部件的並行性,提升了計算機的利用率,既然進程這麼優秀,爲何還要線程呢? 其實,仍是有不少缺陷的,主要體如今兩點上:安全
進程只能在一個時間幹一件事,若是想同時幹兩件事或多件事,進程就無能爲力了。
多線程
進程在執行的過程當中若是阻塞,例如等待輸入,整個進程就會掛起,即便進程中有些工做不依賴於輸入的數據,也將沒法執行。併發
而解決辦法就是讓單個進程,接受請求、等待I/O、處理計算並行起來,這樣很明顯能夠避免同步等待,提升執行效率,在實際操做系統中這樣的機制就是——線程。app
由於要併發,咱們發明了進程,又進一步發明了線程。只不過進程和線程的併發層次不一樣:進程屬於在處理器這一層上提供的抽象;線程則屬於在進程這個層次上再提供了一層併發的抽象。若是咱們進入計算機體系結構裏,就會發現,流水線提供的也是一種併發,不過是指令級的併發。這樣,流水線、線程、進程就從低到高在三個層次上提供咱們所迫切須要的併發!python2.7
除了提升進程的併發度,線程還有個好處,就是能夠有效地利用多處理器和多核計算機。如今的處理器有個趨勢就是朝着多核方向發展,在沒有線程以前,多核並不能讓一個進程的執行速度提升,緣由仍是上面全部的兩點限制。但若是講一個進程分解爲若干個線程,則可讓不一樣的線程運行在不一樣的核上,從而提升了進程的執行速度。異步
例如:咱們常用微軟的Word進行文字排版,實際上就打開了多個線程。這些線程一個負責顯示,一個接受鍵盤的輸入,一個進行存盤等等。這些線程一塊兒運行,讓咱們感受到咱們輸入和屏幕顯示同時發生,而不是輸入一些字符,過一段時間才能看到顯示出來。在咱們不經意間,還進行了自動存盤操做。這就是線程給咱們帶來的方便之處。async
進程是具備必定獨立功能的程序關於某個數據集合上的一次運行活動,進程是系統進行資源分配和調度的一個獨立單位。
線程是進程的一個實體, 是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位.線程本身基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),可是它可與同屬一個進程的其餘的線程共享進程所擁有的所有資源。
一個線程能夠建立和撤銷另外一個線程,同一個進程中的多個線程之間能夠併發執行。
進程和線程的主要差異在於它們是不一樣的操做系統資源管理方式。進程有獨立的地址空間,一個進程崩潰後,在保護模式下不會對其它進程產生影響,而線程只是一個進程中的不一樣執行路徑。線程有本身的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等於整個進程死掉,因此多進程的程序要比多線程的程序 健壯,但在進程切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行而且又要共享某些變量的併發操做,只能用線程,不能用進程。
Python 多進程(multiprocessing)
Unix/Linux操做系統提供了一個fork()
系統調用,它很是特殊。普通的函數調用,調用一次,返回一次,可是fork()
調用一次,返回兩次,由於操做系統自動把當前進程(稱爲父進程)複製了一份(稱爲子進程),而後,分別在父進程和子進程內返回。
子進程永遠返回0
,而父進程返回子進程的ID。這樣作的理由是,一個父進程能夠fork出不少子進程,因此,父進程要記下每一個子進程的ID,而子進程只須要調用getppid()
就能夠拿到父進程的ID。
Python的os
模塊封裝了常見的系統調用,其中就包括fork
,能夠在Python程序中輕鬆建立子進程:
# multiprocessing.py import os print 'Process (%s) start...' % os.getpid() pid = os.fork() if pid==0: print 'I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()) else: print 'I (%s) just created a child process (%s).' % (os.getpid(), pid)
因爲Windows沒有fork
調用,上面的代碼在Windows上沒法運行。
multiprocessing是跨平臺版本的多進程模塊,它提供了一個Process類來表明一個進程對象,下面是示例代碼:
#!/usr/local/python27/bin/python2.7 # coding=utf8 # noinspection PyUnresolvedReferences from multiprocessing import Process import time def f(n): time.sleep(1) print n*n for i in range(10): p = Process(target=f,args=[i,]) p.start()
這個程序若是用單進程寫則須要執行10秒以上的時間,而用多進程則啓動10個進程並行執行,只須要用1秒多的時間。
在通常狀況下多個進程的內存資源是相互獨立的,而多線程能夠共享同一個進程中的內存資源,示例代碼:
#!/usr/local/python27/bin/python2.7 # coding=utf8 # noinspection PyUnresolvedReferences # 經過多進程和多線程對比,進程間內存沒法共享,線程間的內存共享 from multiprocessing import Process import threading import time lock = threading.Lock() def run(info_list,n): lock.acquire() info_list.append(n) lock.release() print('%s\n' % info_list) info = [] for i in range(10): '''target爲子進程執行的函數,args爲須要給函數傳遞的參數''' p = Process(target=run,args=[info,i]) p.start() '''這裏是爲了輸出整齊讓主進程的執行等一會兒進程''' time.sleep(1) print('------------threading--------------') for i in range(10): p = threading.Thread(target=run,args=[info,i]) p.start()
執行結果:
Queue是多進程安全的隊列,可使用Queue實現多進程之間的數據傳遞。put方法用以插入數據到隊列中,put方法還有兩個可選參數:blocked和timeout。若是blocked爲True(默認值),而且timeout爲正值,該方法會阻塞timeout指定的時間,直到該隊列有剩餘的空間。若是超時,會拋出Queue.Full異常。若是blocked爲False,但該Queue已滿,會當即拋出Queue.Full異常。
get方法能夠從隊列讀取而且刪除一個元素。一樣,get方法有兩個可選參數:blocked和timeout。若是blocked爲True(默認值),而且timeout爲正值,那麼在等待時間內沒有取到任何元素,會拋出Queue.Empty異常。若是blocked爲False,有兩種狀況存在,若是Queue有一個值可用,則當即返回該值,不然,若是隊列爲空,則當即拋出Queue.Empty異常。Queue的一段示例代碼:
#!/usr/local/python27/bin/python2.7 # coding=utf8 # noinspection PyUnresolvedReferences # 經過multiprocessing.Queue實現進程間內存共享 from multiprocessing import Process,Queue import time def write(q): for i in ['A','B','C','D','E']: print('Put %s to queue' % i) q.put(i) time.sleep(0.5) def read(q): while True: v = q.get(True) print('get %s from queue' %v) if __name__ == '__main__': q = Queue() pw = Process(target=write,args=(q,)) pr = Process(target=read,args=(q,)) pw.start() pr.start() pr.join() pr.terminate()
執行結果:
Value,Array
#!/usr/local/python27/bin/python2.7 # coding=utf8 # noinspection PyUnresolvedReferences # 經過Value Array 實現進程間的內存共享 from multiprocessing import Value,Array,Process def f(n,a,raw): n.value = 3.14 for i in range(5): a[i] = -a[i] raw.append(9999) print(raw) if __name__ == '__main__': num = Value('d',0.0) arr = Array('i',range(10)) raw_list = range(10) print(num.value) print(arr[:]) print(raw_list) #調用子進程以後,從新打印array和value,值將會發生改變。 而raw_list 普通列表在外層打印則沒有發生改變。 p = Process(target=f,args=(num,arr,raw_list)) p.start() p.join() print(num.value) print(arr[:]) print(raw_list)
執行結果:
Manager
#!/usr/local/python27/bin/python2.7 # coding=utf8 # noinspection PyUnresolvedReferences from multiprocessing import Process,Manager def f(d,l): d[1] = '1' d['aa'] = 'hello World' l.reverse() if __name__ == '__main__': manager = Manager() d = manager.dict() l = manager.list(range(10)) p = Process(target=f,args=(d,l)) p.start() p.join() print(d) print(l)
執行結果:
Pool (進程池)
用於批量建立子進程,能夠靈活控制子進程的數量
#!/usr/local/python27/bin/python2.7 # coding=utf8 # noinspection PyUnresolvedReferences from multiprocessing import Pool import time def f(x): print x*x time.sleep(2) return x*x '''定義啓動的進程數量''' pool = Pool(processes=5) res_list = [] for i in range(10): '''以異步並行的方式啓動進程,若是要同步等待的方式,能夠在每次啓動進程以後調用res.get()方法,也可使用Pool.apply''' res = pool.apply_async(f,[i,]) print('-------:',i) res_list.append(res) pool.close() pool.join() for r in res_list: print(r.get(timeout=5))
Pool對象調用join()方法會等待全部子進程執行完畢,調用join()以前必須先調用close(),調用close()以後就不能繼續添加新的Process了。
Python 多線程(threading)
上面介紹了線程的做用,在python的標準庫中提供了兩個模塊:thread和threading,threading是對thread進行了封裝的高級模塊。啓動一個線程就是把一個函數傳給Thread實例,而後調用start()方法。
先來看一個示例程序:
#!/usr/local/python27/bin/python2.7 # coding=utf8 # noinspection PyUnresolvedReferences from threading import Thread import time def f(n): time.sleep(1) num = n*n print('%s\n' % num) l1 = range(10) for i in l1: p = Thread(target=f,args=(i,)) p.start()
多線程和多進程最大的不一樣在於,多進程中,同一個變量,各自有一份拷貝存在於每一個進程中,互不影響,而多線程中,全部變量都由全部線程共享,因此,任何一個變量均可以被任何一個線程修改,所以,線程之間共享數據最大的危險在於多個線程同時改一個變量,把內容給改亂了。
示例代碼:
#!/usr/local/python27/bin/python2.7 # coding=utf8 # noinspection PyUnresolvedReferences import time, threading balance = 0 def change_it(n): # 先加後減,結果應該爲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
定義的這個balance最後執行結果理論上是0,可是多個線程交替執行時就不必定是0了,由於修改balance須要多條語句,而執行這幾條語句時,線程可能中斷,從而致使多個線程把同一個對象的內容改亂了。
兩個線程同時一存一取,就可能致使數據不對,若是要確保balance計算正確,就須要給change_i()上一把鎖,確保一個線程在修改balance的時候,別的線程必定不能改。
下面看示例代碼,建立鎖經過threading.Lock()來實現:
#!/usr/local/python27/bin/python2.7 # coding=utf8 # noinspection PyUnresolvedReferences import time, threading balance = 0 lock = threading.Lock() def change_it(n): # 先加後減,結果應該爲0: global balance balance = balance + n balance = balance - n def run_thread(n): for i in range(100000): lock.acquire() try: change_it(n) finally: lock.release() 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
生產者消費者模型
#!/usr/local/python27/bin/python2.7 # coding=utf8 # noinspection PyUnresolvedReferences from threading import Thread from Queue import Queue import time class procuder(Thread): ''' @:param name:生產者名稱 @:param queue:容器 ''' def __init__(self,name,queue): self.__Name = name self.__Queue = queue super(procuder,self).__init__() def run(self): while True: if self.__Queue.full(): time.sleep(1) else: self.__Queue.put('baozi') time.sleep(1) print('%s 生產了一個包子' %(self.__Name)) class consumer(Thread): def __init__(self,name,queue): self.__Name = name self.__Queue = queue '''執行父類的構造方法''' super(consumer,self).__init__() def run(self): while True: if self.__Queue.empty(): time.sleep(1) else: self.__Queue.get() time.sleep(1) print('%s 吃了一個包子' %(self.__Name)) que = Queue(maxsize=100) tuchao1 = procuder('小塗1',que) tuchao1.start() tuchao2 = procuder('小塗2',que) tuchao2.start() tuchao3 = procuder('小塗3',que) tuchao3.start() for i in range(20): name = '小喻%d' %(i,) temp = consumer(name,que) temp.start()
python 多線程開發之事件
#!/usr/local/python27/bin/python2.7 # coding=utf8 # noinspection PyUnresolvedReferences import threading import time def producer(): print ('店家:本店出售包子。。。。') '''觸發事務等待''' event.wait() '''清楚事務狀態''' event.clear() print ('小明:老闆,請給我一個肉包子。。。。') print ('店家:正在生產包子,請稍等xxxxxxxxx') time.sleep(6) print ('店家:你要的包子已經好了------') event.set() def consumer(): print('小明去買包子。。。。') '''解除事務等待''' event.set() time.sleep(2) while True: if event.isSet(): print ('謝謝!') break else: print ('包子還沒好嗎? 等待中...') time.sleep(1) event = threading.Event() p = threading.Thread(target=producer) c = threading.Thread(target=consumer) c.start() p.start()
執行結果:
GIL鎖(Global Interpreter Lock)
Python的線程雖然是真正的線程,但解釋器執行代碼時,有一個GIL鎖:Global Interpreter Lock,任何Python線程執行前,必須先得到GIL鎖,而後,每執行100條字節碼,解釋器就自動釋放GIL鎖,讓別的線程有機會執行。這個GIL全局鎖實際上把全部線程的執行代碼都給上了鎖,因此,多線程在Python中只能交替執行,即便100個線程跑在100核CPU上,也只能用到1個核。
GIL是Python解釋器設計的歷史遺留問題,一般咱們用的解釋器是官方實現的CPython,要真正利用多核,除非重寫一個不帶GIL的解釋器。
因此,在Python中,可使用多線程,但不要期望能有效利用多核。若是必定要經過多線程利用多核,那隻能經過C擴展來實現,不過這樣就失去了Python簡單易用的特色。
不過,也不用過於擔憂,Python雖然不能利用多線程實現多核任務,但能夠經過多進程實現多核任務。多個Python進程有各自獨立的GIL鎖,互不影響。
參考文獻:
http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143192823818768cd506abbc94eb5916192364506fa5d000http://www.cnblogs.com/hazir/archive/2011/05/09/2447287.html