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.線程隊列 *** 隊列 堆棧 優先級隊列三、單線程下實現併發(***) 什麼是協程 併發 併發實現的本質=切換+保存狀態(兩類切換) 高性能分析: 爲何須要協程 如何實現協程(三種) 協程的應用場景: 總結點:====================================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複習 隊列 堆棧 優先級隊列三、單線程下實現併發(***) 什麼是協程: 單線程下實現併發,在應用程序級別實現多個任務之間切換+保存狀態 併發:看起來是同時運行 併發實現的本質=切換+保存狀態 切換: 一、遇到IO阻塞會切換(能夠提高運行效率) 二、佔用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])