Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

13.並行、平行與非同步

認識並行、平行與非同步
使用 threading 模組
使用 multiprocessing 模組
使用 concurrent.futures模組
運用 async、await 與 asyncio

  • Identifiez-vous pour voir les commentaires

  • Soyez le premier à aimer ceci

13.並行、平行與非同步

  1. 1. 13.並行、平行與非同步 • 學習目標 – 認識並行、平行與非同步 – 使用 threading 模組 – 使用 multiprocessing 模組 – 使用 concurrent.futures模組 – 運用 async、await 與 asyncio 2
  2. 2. 並行 • 多個流程可以並行(Concurrency)處理, 也就是從使用者的觀點來看,會是同時 「執行」各個流程 • 然而實際上,是同時「管理」多個流程 3
  3. 3. 簡介執行緒 4
  4. 4. 5
  5. 5. • 雖可以繼承 threading.Thread, 在 __init__()呼叫 super().__init__(), 並在類別中定義run()方法來實作執行緒 • 不過是不建議的,因為這會使得你的流程與 threading.Thread 產生相依性 6
  6. 6. 7
  7. 7. 8
  8. 8. • python 直譯器同時間只允許執行一個執行 緒,因此並不是真正的平行(Parallel)處 理,只不過「有時候」切換速度快到人類 感覺上像是同時處理罷了 • 執行緒適用的場合之一,就是非計算密集 的場合,因為與其等待某個阻斷作業完成, 不如趁著等待的時間來進行其他執行緒 9
  9. 9. 10
  10. 10. 11
  11. 11. • 對於計算密集的任務,使用執行緒不見得 會提高處理效率,反而容易因為直譯器必 須切換執行緒而耗費不必要的成本,使得 效率變差。 12
  12. 12. • 如果主執行緒中啟動了額外執行緒,預設 會等待被啟動的所有執行緒都執行完才中 止程式。 • 如果一個 Thread 建立時,指定了daemon 參數為 True,在所有的非 Daemon 的執 行緒都結束時,程式就會直接終止 • 如果需要在背景執行一些常駐任務,就可 以指定 daemon 參數為 True。 13
  13. 13. • 當執行緒使用 join() 加入至另一執行緒 時,另一執行緒會等待被加入的執行緒工 作完畢,然後再繼續它的動作 14
  14. 14. • 如果要停止執行緒,必須自行實作,讓執 行緒跑完應有的流程 15
  15. 15. 競速、鎖定、死結 • 如果執行緒之間不需要共享資料, 或者共 享的資料是不可變動(Immutable)的型 態,事情會單純一些 • 然而,執行緒之間經常得共用一些可變動 狀態的資料… • 要是執行緒之間需要共享的是可變動狀態 的資料,就會有可能發生競速狀況… 16
  16. 16. 17
  17. 17. • 若要避免競速的情況發生,就必須資源被 變更與取用時的關鍵程式碼進行鎖定 18
  18. 18. 19
  19. 19. • threading.Lock 實作了情境管理器協定, 可以搭配 with 來簡化 acquire() 與 release() 的呼叫 20
  20. 20. 21
  21. 21. • 執行緒無法取得鎖定時會造成阻斷,不正 確地使用 Lock 有可能造成效能低落,另一 問題則是死結 22
  22. 22. 23
  23. 23. • threading.RLock 實現了可重入鎖 (Reentrant lock) • 同一執行緒可以重複呼叫同一個 threading.RLock 實例的 acquire() 而不被阻斷 • release()時也要有對應於 acquire() 的次數,方可以完全解除鎖定 • threading.RLock 也實作了情境管理器 協定,可搭配 with 來使用 24
  24. 24. • 另一個經常使用的鎖定機制是 threading.Condition • 某個執行緒在透過 acquire() 取得鎖定之 後,若需要在特定條件符合之前等待,可 以呼叫 wait() 方法,這會釋放鎖定 • 若其他執行緒的運作促成特定條件成立, 可以呼叫同一 threading.Condition 實 例的 notify(),通知等待條件的一個執 行緒可取得鎖定 25
  25. 25. • 若等待中的執行緒取得鎖定,就會從上次 呼叫 wait() 方法處繼續執行 • 如果等待中的執行緒有多個,還可以呼叫 notify_all(),這會通知全部等待中的 執行緒爭取鎖定 26
  26. 26. 27
  27. 27. 28
  28. 28. • 如果需要這種一進一出,在執行緒之間交 換資料的方式,Python 標準程式庫中提供 了 queue.Queue 29
  29. 29. • 建立Semaphore 可指定計數器初始值 • 每呼叫一次 acquire(),計數器值遞減一, 在計數器為0 時若呼叫了 acquire(),執 行緒就會被阻斷 • 每呼叫一次 release(),計數器值遞增一, 如果 release()前計數器為 0,而且有執 行緒正在等待,在 release() 並遞增計數 器之後,會通知等待中的執行緒 30
  30. 30. • 可以設定一個 Barrier 並指定數量 • 如果有執行緒先來到這個柵欄,它必須等 待其他執行緒也來到這個柵欄 • 指定的執行緒數量達到,全部執行緒才能 繼續往下執行 31
  31. 31. 32
  32. 32. 平行 • 針對計算密集式的運算,若能在一個新的 行程(Process)平行(Parallel)運行,在 今日電腦普遍都有多個核心的情況下,就 有機會跑得更快一些。 33
  33. 33. • subprocess 模組可以讓你在執行 Python 程式的過程中,產生新的子行程 34
  34. 34. • 從 Python 3.5 開始,建議使用 run() 函 式來呼叫子行程 • subprocess.run() 執行之後會傳回 CompletedProcess 實例 • 若想要能取得標準輸出的執行結果: 35
  35. 35. • 如果子行程必須接受標準輸入: 36
  36. 36. • subprocess.run() 的底層是透過 subprocess.Popen() 實作出來的 37
  37. 37. • subprocess.Popen() 執行程式,會立 即傳回 Popen 實例,不會等待子行程結束 38
  38. 38. 39
  39. 39. • 如果想要以子行程來執行函式,然而使用 類似 threading 模組的 API 介面,那麼 可以使用 multiprocessing 模組 40
  40. 40. 41
  41. 41. • 建議在使用 multiprocessing 模組時, 最好的方式是不要共享狀態 • 然而有時行程之間難免需要進行溝通, multiprocessing.Queue 是執行緒與行 程安全的,實作了必要的鎖定機制 42
  42. 42. 43
  43. 43. 44
  44. 44. • multiprocessing.Lock 也實作了情境 管理器協定 45
  45. 45. 46
  46. 46. 非同步 • Python 3.2 新增 concurrent.futures 模組,它提供了執行緒或行程高階封裝, 也便於實現非同步的任務 • 從 Python 3.5 之後,提供了async、 await 等語法,以及 asyncio 模組的支援, 如果非同步任務涉及大量的輸入輸出,可 以善用這些特性 47
  47. 47. 使用 concurrent.futures • 提供了 ThreadPoolExecutor 與 ProcessPoolExecutor 等高階API,分 別為執行緒與行程提供了工作者池的服務 48
  48. 48. 49
  49. 49. • 對於計算密集式的任務,可以使用 ProcessPoolExecutor 50
  50. 50. 51
  51. 51. • 使用 map() 方法來簡化程式的撰寫 52
  52. 52. Future 與非同步 • 獨立於程式主流程的任務、事件生成,以 及處理事件的方式,稱為非同步 • 使用執行緒或行程時,若想實現非同步概 念,方式之一是採用註冊回呼函式 53
  53. 53. 54
  54. 54. • executor 的submit() 執行過後會傳回 Future,擁有 add_done_callback() 55
  55. 55. • 在下載的同時實現簡單的進度列: 56
  56. 56. 略談 yield from 與非同步 57
  57. 57. • 若後續處理為數個非同步函式的話,整個 流程會馬上陷入難以理解的狀態 • Python 3.3 時新增了 yield from 語法, Python 3.4 的 asyncio 與某些第三方程式 庫,曾基於這個語法提出了解決方案 • Python 3.5 後建議不要使用 yield from, 建議使用 async、await 58
  58. 58. 59
  59. 59. yield from 與 Future 60
  60. 60. • 有多個非同步函式,顯然就需要個迴圈: 61
  61. 61. • 程式執行雖然是非同步,然而撰寫風格上 卻像是循序 • 若有人想呼叫 asyncTasks() 呢?甚至是 在流程上組合多個這類的函式? 62
  62. 62. 63
  63. 63. 64
  64. 64. Asyncio與並行 • 並非一定要多執行緒或多行程,才能實現 並行 • 多執行緒或多行程只是實現並行比較容易 • 只要執行環境支援,在單一行程、單一執 行緒中,也有可能實現並行 – 在遇到阻斷操作時會讓出(yield)流程控制權 給呼叫函式者,那呼叫函式的一方,就可以繼 續下個並行任務的啟動 65
  65. 65. • 如果在定義函式時,加上了 async 關鍵字, 呼叫該函式並不會馬上執行函式流程,而 是傳回一個 coroutine 物件 • 想要執行函式中定義的流程,可以透過 asyncio.run() 函式 66
  66. 66. • async def定義的函式是個非同步任務 • 執行asyncio.run()會阻斷,直到該執行 緒完成指定的任務 • 如果有多個任務要指定呢?Python 3.7以 後可以使用asyncio.create_task() 67
  67. 67. • 必須在async def函式中呼叫 asyncio.create_task() • 也就是說,若有多個要執行的非同步任務, 必須在async def的函式中組織 • asyncio.create_task()不會阻斷執行 緒 68
  68. 68. 69
  69. 69. 事件迴圈 • 大部份基於Asyncio的函式,建議使用 asyncio.run()來執行 • 在Python 3.7之前沒有 asyncio.create_task(),就是透過這種 方式來建立多個任務 70
  70. 70. • 方才第二個REPL範例,若要直接操作事件 迴圈 71
  71. 71. • 在Python 3.7以後,若不想傳遞事件迴圈 實例給async def函式,可以使用 asyncio.get_running_loop() 72
  72. 72. async、await 與非同步 • async 用來標示函式執行時是非同步,也 就是函式中定義定義了獨立於程式主流程 的任務 • 後續若要在任務完成時,做進一步的處理 也是可行的 • 從 Python 3.5 開始,可以使用await 73
  73. 73. 74
  74. 74. • 就語義上,若想等待 async 函式執行完後, 再執行後續的流程,可以使用await • async 函式的任務完成後若有傳回值,會 成為 await 的傳回值 75
  75. 75. 76
  76. 76. 77
  77. 77. 非同步產生器與 async for 78 • 迭代產生器時會以阻斷方式讀取 URL 後傳 回 bytes
  78. 78. 79
  79. 79. 情境管理器與 async with 80
  80. 80. 81

×