先來欣賞一段有趣的漫畫對話哦
————— 第二天 —————





——————————————————






什么事行程和執行緒
有一定基礎的小伙伴們肯定都知道行程和執行緒,
行程是什么呢?
直白地講,行程就是應用程式的啟動實體,比如我們運行一個游戲,打開一個軟體,就是開啟了一個行程,
行程擁有代碼和打開的檔案資源、資料資源、獨立的記憶體空間,
執行緒又是什么呢?
執行緒從屬于行程,是程式的實際執行者,一個行程至少包含一個主執行緒,也可以有更多的子執行緒,
執行緒擁有自己的堆疊空間,

有了行程為什么還需要執行緒
因為行程不能同一時間只能做一個事情
什么是執行緒
執行緒是作業系統調度的最小單位
執行緒是行程正真的執行者,是一些指令的集合(行程資源的擁有者)
同一個行程下的讀多個執行緒共享記憶體空間,資料直接訪問(資料共享)
為了保證資料安全,必須使用執行緒鎖
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三程三器的那些事》
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/189230.html
標籤:Python

