先來欣賞一段有趣的漫畫對話哦html
————— 次日 —————python
——————————————————mysql
有必定基礎的小夥伴們確定都知道進程和線程。git
進程是什麼呢?github
直白地講,進程就是應用程序的啓動實例。好比咱們運行一個遊戲,打開一個軟件,就是開啓了一個進程。sql
進程擁有代碼和打開的文件資源、數據資源、獨立的內存空間。編程
線程又是什麼呢?安全
線程從屬於進程,是程序的實際執行者。一個進程至少包含一個主線程,也能夠有更多的子線程。併發
線程擁有本身的棧空間。框架
有了進程爲何還須要線程
由於進程不能同一時間只能作一個事情
什麼是線程
線程是操做系統調度的最小單位
線程是進程正真的執行者,是一些指令的集合(進程資源的擁有者)
同一個進程下的讀多個線程共享內存空間,數據直接訪問(數據共享)
爲了保證數據安全,必須使用線程鎖
GIL全局解釋器鎖
在python全局解釋器下,保證同一時間只有一個線程運行
防止多個線程都修改數據
線程鎖(互斥鎖)
GIL鎖只能保證同一時間只能有一個線程對某個資源操做,但當上一個線程還未執行完畢時可能就會釋放GIL,其餘線程就能夠操做了
線程鎖本質把線程中的數據加了一把互斥鎖
mysql中共享鎖 & 互斥鎖
mysql共享鎖:共享鎖,全部線程都能讀,而不能寫
mysql排它鎖:排它,任何線程讀取這個這個數據的權利都沒有
加上線程鎖以後全部其餘線程,讀都不能讀這個數據
有了GIL全局解釋器鎖爲何還須要線程鎖
由於cpu是分時使用的
死鎖定義
兩個以上的進程或線程在執行過程當中,因爭奪資源而形成的一種互相等待的現象,若無外力做用,它們都將沒法推動下去
進程和線程的痛點
線程之間是如何進行協做的呢?
最經典的例子就是生產者/消費者模式:
若干個生產者線程向隊列中寫入數據,若干個消費者線程從隊列中消費數據。
應用程序像工廠,進程像車間,線程像工人
一個進程中的線程能夠在不一樣cpu上執行,一個線程不能同時在兩個cpu上執行
python中有一個全局解釋器鎖(GIL global interpreter lock),他就像一把鎖所在進程上,保證同一時刻,一個進程中不管有多少線程,只能保證有一個線程出來
隨着cpu核數的增長,python中能夠建立多個進程,一個進程中有一個全局解釋器,這樣一個cpu跑一個進程,一個進程同一時刻只能出一個線程,達到並行的目的
線程和進程的區別 :
線程是能夠共享內存的,而進程不能夠共享內存,一個進程就像一個應用程序,例如qq和淘寶,這屬於兩個進程,在QQ中發信息確定不會發到淘寶中,可是在qq中存在不少的線程,他們能夠共享內存,他們都屬於一個進程,
假如eclipse在運行時佔中400M的內存空間,那麼這個進程下的線程的共享空間爲這個主進程所佔用的內存空間,就是eclipse的線程的內存共享就爲400M
線程沒有辦法單獨執行,線程必須在進程中
多進程之間的通訊:
多進程之間沒法直接通訊,它必須藉助一個第三方的橋樑
1 隊列 from multiprocessing import Process, Queue def f(q,n): q.put([ n, 'hello']) if __name__ == '__main__': q = Queue() for i in range(5): p = Process(target=f, args=(q,i)) p.start() print q.get()
線程的安全問題,假如如今有十個線程,同時修改一個變量,讓這個變量+1,那麼會出現一個問題,假如A線程正在修改變量,如今變量爲m=2,A線程操做m,目的是讓A+1,然而A修改到一半的時候,線程B又進來了,線程B此時取的m的值也是2,A修改完了m=3,B修改完了m仍是等於3 ,很是不安全
解決辦法,就是加一把鎖,形象的例子:
假如如今有10我的要上廁所,鑰匙在我手裏,這個時候A進來,他把門關上了,別人就進不去,只有等A完過後其餘人才能進來,這個門就是控制線程的那把鎖
代碼實現就經過一個lock來實現,lock 只容許一個線程訪問
lock=threading.Lock()
lock.acquire()
lock.release()
代碼以下
val=0 #全局變量 def run(self,): #lock.acquire() time.sleep(1) global val lock.acquire() val+=1 print '%s' %val lock.release() //這個地方執行完了必須relese,讓後面的線程繼續執行,不然後面沒法執行 lock=threading.Lock() for i in range(100): t=threading.Thread(target=run,args=(i,)) t.start()
假如一個廁所門裏有兩個坑,容許兩我的同時上廁所,即容許兩個線程同時修改變量,那麼用samp
samp = threading.BoundedSemaphore(3)
samp.acquire()
samp.release()
當參數爲1時,實際上就等於lock
val=0 #全局變量 def run(self,): #lock.acquire() time.sleep(1) global val samp.acquire() val+=1 print '%s' %val samp.release() lock=threading.Lock() samp = threading.BoundedSemaphore(3) for i in range(100): t=threading.Thread(target=run,args=(i,)) t.start()
全局變量和局部變量
全局變量是在方法以外定義的變量,方法內部不能修改全局變量
例如 val=0 #全局變量
def run(self,n)
val+=1
程序會報錯,方法內不能直接就該全局變量
在假如 val=0 #全局變量
def run(self,n)
val=1
print val
輸出結果會是1,這是爲何呢,應爲此時函數內部的val與全局變量val並非一個,函數內部的 val是局部變量,函數外部的val爲全局變量
假如 執行c=run(‘haha’)
print val
會輸出 0
1
假如要在函數內從新定義局部變量
必須在函數中從新聲明全局變量
val=0 #全局變量 def run(self,n) global val val=1 print val
join() 執行到join時,例如執行到join(3)而後就在這個地方等待三秒,
from threading import Thread import time class MyThread(Thread): def run(self): time .sleep(5) print '我是線程' def bar(): print 'bar' t1=MyThread(target=bar ) t1.start() t1.join(3) print ‘over'
採用面向類的生產者消費者模型
#_*_ coding:utf-8 _*_ from threading import Thread from Queue import Queue import time class Producer(Thread): def __init__(self,name,queue): self.__name=name self.__queue=queue super(Producer,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) baozi_make1=Producer('mahzongyi',que) baozi_make1.start() baozi_make2=Producer('mahzongyi1',que) baozi_make2.start() baozi_make3=Producer('mahzongyi2',que) baozi_make3.start() for item in range(20): name = 'lazup%d'%(item) temp=consumer(name,que) temp.start()
上面的代碼正確地實現了生產者/消費者模式,可是卻並非一個高性能的實現。爲何性能不高呢?緣由以下:
1.涉及到同步鎖。
2.涉及到線程阻塞狀態和可運行狀態之間的切換。
3.涉及到線程上下文的切換。
以上涉及到的任何一點,都是很是耗費性能的操做。
協程,英文Coroutines,是一種比線程更加輕量級的存在。正如一個進程能夠擁有多個線程同樣,一個線程也能夠擁有多個協程。
協程微線程,纖程,本質是一個單線程
協程能在單線程處理高併發
線程遇到I/O操做會等待、阻塞,協程遇到I/O會自動切換(剩下的只有CPU操做)
線程的狀態保存在CPU的寄存器和棧裏而協程擁有本身的空間,因此無需上下文切換的開銷,因此快、
爲甚麼協程可以遇到I/O自動切換
協程有一個gevent模塊(封裝了greenlet模塊),遇到I/O自動切換
協程缺點
沒法利用多核資源:協程的本質是個單線程,它不能同時將 單個CPU 的多個核用上,協程須要和進程配合才能運行在多CPU上
線程阻塞(Blocking)操做(如IO時)會阻塞掉整個程序
最重要的是,協程不是被操做系統內核所管理,而徹底是由程序所控制(也就是在用戶態執行)。
這樣帶來的好處就是性能獲得了很大的提高,不會像線程切換那樣消耗資源。
既然協程這麼好,它究竟是怎麼來使用的呢?
咱們來看一看python當中對協程的實現案例,一樣以生產者消費者模式爲例:
這段代碼十分簡單,即便沒用過python的小夥伴應該也能基本看懂。
代碼中建立了一個叫作consumer的協程,而且在主線程中生產數據,協程中消費數據。
其中 yield 是python當中的語法。當協程執行到yield關鍵字時,會暫停在那一行,等到主線程調用send方法發送了數據,協程纔會接到數據繼續執行。
可是,yield讓協程暫停,和線程的阻塞是有本質區別的。協程的暫停徹底由程序控制,線程的阻塞狀態是由操做系統內核來進行切換。
所以,協程的開銷遠遠小於線程的開銷。
有哪些編程語言應用到了協程呢?咱們舉幾個栗子:
Lua語言
Lua從5.0版本開始使用協程,經過擴展庫coroutine來實現。
Python語言
正如剛纔所寫的代碼示例,python能夠經過 yield/send 的方式實現協程。在python 3.5之後,async/await 成爲了更好的替代方案。
Go語言
Go語言對協程的實現很是強大而簡潔,能夠輕鬆建立成百上千個協程併發執行。
Java語言
如上文所說,Java語言並無對協程的原生支持,可是某些開源框架模擬出了協程的功能,有興趣的小夥伴能夠看一看Kilim框架的源碼:
https://github.com/kilim/kilim
詳細介紹請閱讀個人分類Python基礎下的《Python三程三器的那些事》