https://blog.csdn.net/freeking101/article/details/88119858
1 同步調用 異步調用+回調機制
提交任務的兩種方式:
什么是同步異步
任務執行的三種狀態:
同步調用vs阻塞,兩種不同的'等'的效果
異步回調 ******
什么是異步回調?
為什么需要回調?(比如 燒水壺,水燒開后 水壺會發出響聲)
注意點:
回調函數什么時候被執行?
誰在執行回調函數?
線程的異步回調
同步:
#所謂同步,就是在發出一個功能調用時,在沒有得到結果之前,該調用就不會返回。
按照這個定義,其實絕大多數函數都是同步調用。但是一般而言,
我們在說同步、異步的時候,特指那些需要其他部件協作或者需要一定時間完成的任務。
#舉例:
#1. multiprocessing.Pool下的apply #發起同步調用后,就在原地等着任務結束,
根本不考慮任務是在計算還是在io阻塞,總之就是一股腦地等任務結束
#2. concurrent.futures.ProcessPoolExecutor().submit(func,).result()
#3. concurrent.futures.ThreadPoolExecutor().submit(func,).result()
異步:
#異步的概念和同步相對。當一個異步功能調用發出后,調用者不能立刻得到結果。
當該異步功能完成后,通過狀態、通知或回調來通知調用者。如果異步功能用狀態來通知,
那么調用者就需要每隔一定時間檢查一次,效率就很低
(有些初學多線程編程的人,總喜歡用一個循環去檢查某個變量的值,這其實是一 種很嚴重的錯誤)。
如果是使用通知的方式,效率則很高,因為異步功能幾乎不需要做額外的操作。至於回調函數,其實和通知沒太多區別。
#舉例:
#1. multiprocessing.Pool().apply_async() #發起異步調用后,並不會等待任務結束才返回,
相反,會立即獲取一個臨時結果(並不是最終的結果,可能是封裝好的一個對象)。
#2. concurrent.futures.ProcessPoolExecutor(3).submit(func,)
#3. concurrent.futures.ThreadPoolExecutor(3).submit(func,)
阻塞:
#阻塞調用是指調用結果返回之前,當前線程會被掛起(如遇到io操作)。
函數只有在得到結果之后才會將阻塞的線程激活。有人也許會把阻塞調用和同步調用等同起來,
實際上他是不同的。對於同步調用來說,很多時候當前線程還是激活的,只是從邏輯上當前函數沒有返回而已。
#舉例:
#1. 同步調用:apply一個累計1億次的任務,該調用會一直等待,
直到任務返回結果為止,但並未阻塞住(即便是被搶走cpu的執行權限,那也是處於就緒態);
#2. 阻塞調用:當socket工作在阻塞模式的時候,
如果沒有數據的情況下調用recv函數,則當前線程就會被掛起,直到有數據為止。
非阻塞:
#非阻塞和阻塞的概念相對應,指在不能立刻得到結果之前也會立刻返回,同時該函數不會阻塞當前線程。
小結:
#1. 同步與異步針對的是函數/任務的調用方式:同步就是當一個進程發起一個函數(任務)調用的時候,
一直等到函數(任務)完成,而進程繼續處於激活狀態。而異步情況下是當一個進程發起一個函數(任務)調用的時候,
不會等函數返回,而是繼續往下執行當,函數返回的時候通過狀態、通知、事件等方式通知進程任務完成。
#2. 阻塞與非阻塞針對的是進程或線程:阻塞是當請求不能滿足的時候就將進程掛起,而非阻塞則不會阻塞當前進程
2.線程隊列 ***
隊列
堆棧
優先級隊列
3、單線程下實現並發(***)
什么是協程
並發
並發實現的本質=切換+保存狀態(兩類切換)
高性能分析:
為什么需要協程
如何實現協程(三種)
協程的應用場景:
總結點:
====================================
1 同步調用 異步調用+回調機制
提交任務的兩種方式:
同步調用 :提交任務必須等待任務完成,才能執行下一行
異步調用 :提交任務不需要等待任務完成,立即執行下一行
線程任務執行的三種狀態:
阻塞
阻塞 遇到了IO操作 失去了CPU的執行權
非阻塞:
就緒
運行
同步調用vs阻塞,兩種不同的'等'的效果
同步調用的等 比如經過上千億次計算,運行時間過長導致,被操作系統拿走執行權限,處於就緒態,非阻塞
阻塞的等 比如經過IO操作,sleep了100秒,這是阻塞
異步回調 ******
什么是異步回調?
發起了一個異步任務 子線程或子進程任務完成后 調用一個函數來通知任務發起方
為什么需要回調?(比如 燒水壺,水燒開后 水壺會發出響聲)
由於任務是異步執行 任務發起方不知道啊什么時候完成
所以使用回調的方式告訴發起方任務執行結果 其他方式也可以將數據交還給主進程
1.shutdown 主進程會等到所有任務完成 # 類似於join的功能pool.shutdown(wait=True)
2.result函數 會阻塞直到任務完成
都會阻塞 導致效率降低 所以使用回調
注意點:
回調函數什么時候被執行? 子進程任務完成時
誰在執行回調函數? 主進程
線程的異步回調:
使用方式都相同 唯一的不同是執行回調函數 是子線程在執行
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
pool = ThreadPoolExecutor()
def get_data_task(url):
return text
def parser_data(f):
print(f.result())
if __name__ == '__main__':
f = pool.submit(get_data_task,url) #get_data_task生產數據
f.add_done_callback(parser_data) #parser_data處理數據
2.線程隊列 *** 見38復習
隊列
堆棧
優先級隊列
3、單線程下實現並發(***)
什么是協程:
單線程下實現並發,在應用程序級別實現多個任務之間切換+保存狀態
並發:看起來是同時運行
並發實現的本質=切換+保存狀態
切換:
1、遇到IO阻塞會切換(可以提升運行效率)
2、占用cpu時間過長或者有一個優先級更高的任務搶走了cpu
優點:
1.協程的切換開銷更小,屬於程序級別的切換,操作系統完全感知不到,因為更加輕量級
2.單線程內就可以實現並發的效果,最大限度地利用cpu
缺點:
1.協程的本質是單線程下,無法利用多核,可以是一個程序開啟多個進程,每個進程內開啟多個線程,每個線程下開啟協程
2.協程指的是單個線程,因而一旦協程出現阻塞,將會阻塞整個線程
高性能分析:
單純地切換,或者說么有遇到io操作也切換,反而會降低效率
檢測單線程下的IO行為,實現遇到IO立即切換到其他任務執行
為什么用協程? 多線程實現並發 有什么問題?
TCP程序中 處理客戶端的連接 需要子線程 但是子線程依然會阻塞
一旦阻塞 CPU切走 但是無法保證是否切到當前程序
提高效率的解決方案 是想辦法盡可能多的占用CPU
當程序遇到阻塞時 切換到別的任務 注意使用程序內切換
協程的實現
1 yield 把函數做成了生成器 生成器會自動保存狀態
2 greenlet 幫我們封裝yield 可以實現任務切換
創建對象時 制定任務就是函數 在函數中手動來switch切換任務 不能檢測到IO行為
3 gevent 封裝了grennlet 既能夠切換執行 也能檢測IO
使用gevent需要配合monkey補丁 monkey補丁內部將原本阻塞的模塊 替換為了非阻塞的
monkey必須放在導入(需要檢測IO的模塊)模塊之前
monkey.patch_all()
gevent核心函數spawn(函數名)
join讓主線程等待所有任務執行完成才結束
協程的應用場景:
(沒有IO絕對不使用協程) TCP 多客戶端實現方式
1.來一個客戶端就來一個進程 資源消耗較大
2.來一個客戶端就來一個線程 也不能無限開
3.用進程池 或 線程池 還是一個線程或進程只能維護一個連接
4.協程 一個線程就可以處理多個客戶端 遇到io就切到另一個
總結協程特點:
必須在只有一個單線程里實現並發,(將io阻塞時間用於執行計算 可以提高效率 原理:一直使用CPU直到超時)
修改共享數據不需加鎖
用戶程序里自己保存多個控制流的上下文棧
附加:一個協程遇到IO操作自動切換到其它協程
(如何實現檢測IO,yield、greenlet都無法實現,就用到了gevent模塊(select機制))
from gevent import monkey;monkey.patch_all()
import gevent
def eat():
print('eat food 1')
time.sleep(2)
print('eat food 2')
def play():
print('play 1')
time.sleep(1)
print('play 2')
g1=gevent.spawn(eat)
g2=gevent.spawn(play)
gevent.joinall([g1,g2])