###### tags: `Python` # Python 的多工--並行與平行處理 Python 提供有多種可以達成多工的方法, 讓我們可以依照需求選擇適當的方式。在 Python 中, 多工的單位是**函式**, 多工的意思就是要讓多個函式能**在同一個期間內都有機會執行**。 ## 沒有多工的情況 假設程式中有兩個處理獨立任務的 A、B 函式, A 函式中會送出網頁請求, 等待並接收網頁後完成後續工作。如果沒有加上任何多工機制, 叫用任何函式都只能等待函式結束, 才能繼續往下執行, 因此不論哪個函式先執行, 另外一個函式就只能乾等待, 像是這樣 ('=' 表示 A 函式等待網頁送來的時間): ``` ... A() B() ... A B | ]-> A 先執行 | = ]-> A 送出網頁請求並等待接收 | ]-> A 結束 | ]-> 換 B 執行 | | | | ]-> B 結束 ``` 這就像是兩個人都想玩同一台遊戲機, 如果沒有相關機制, 其中一個人只能等另一個人不玩了才能玩。如果想要讓兩個人都不用等太久都可以玩到, 就必須採用多工的機制。 ## 並行處理 (concurrent processing) 廣義來說, 只要能讓多個函式的執行期間相互交疊, 就可以有多工的感受, 這稱為**並行處理 (concurrent processing)**。最簡單的方法就是**切割時間**, 讓多個函式可以**輪流**執行一小段時間, 這種方式稱為**搶佔式多工 (preemtive multitasking)**, 像是這樣: ``` A B | ]-> A 先執行一小段時間 | ]-> 換 B 執行一小段時間 | ]-> A 再執行一小段時間 | ]-> B 再執行一小段時間 = ]-> A 送出網頁請求並等待接收網頁 | ]-> B 再執行一小段時間 | ]-> A 執行到結束 | ]-> B 再執行一小段時間 | ]-> B 執行到結束 ``` 這就像是遊戲機設定好固定 10 分鐘就會強制儲存進度結束遊戲, 強迫換另一個人玩一樣。這樣的好處是, 兩個人**都可以在限定的時間內玩到遊戲機**, 而且如果在等待的時間剛好想要去洗手間, 就可以善用時間, **不需要枯等**。 以 A、B 函式為例, 假設 A 函式是在第二段時間內送出網頁請求, 接著輪到 B 函式執行, B 函式執行的期間網頁傳輸的工作仍會由系統底層的網路驅動程式進行, 等到再輪回 A 函式時, 網頁可能已經傳輸完了。由於網頁傳輸與 B 函式執行的時間重疊, 就會縮短整體執行時間, 像是這樣: ``` A B | | | ]-> A 送出網頁請求 = | ]-> B 執行時底層網路傳輸也在進行 | ]-> 網頁已收到不用再等 | | | ``` 在 Python 中提供這項機制的是 [threading](https://docs.python.org/3/library/threading.html) 模組, 以多個**緒程 (thread)** 物件讓多個函式輪流執行。 這種強制切換的方式有個缺點, 因為是**硬性切換**, 假設輪到玩遊戲機時, 卻剛好想上洗手間, 也只能犧牲一些時間去洗手間, 但去洗手間的過程中遊戲機卻**閒置著沒人玩**。此外, 強置中斷的時間點可能玩家正在破關大門, 但卻被迫暫停, 也無法盡興。 以 A、B 函式來說, 如果 A 函式是在第三小段時間的一開始才送出請求, 整個第三小段的其他時間就幾乎都在等待網頁送回, B 函式也因為還沒輪到執行而只能空等。結果反而因為多次切換函式執行, 額外增加切換時間, 讓整體的執行時間變長: ``` A B | | | | = ]-> A 送出請求並等待網頁傳輸 | B 尚未輪到只能空等無法執行 | | | ``` 為了解決這個問題, 也可以採用另一種機制, 稱為**協作式 (cooperate) 多工**, 玩遊戲機的人事先約定好玩到遊戲結束 (死掉或破關) 才換手, 如果中途想要上洗手間, 也要乖乖換人玩, 只要大家都遵守規則, 就能讓大家都玩得盡興、也可以在必要的時候去做其他事, 不會讓別人空等。這個方式有賴於大家協力合作, 所以稱為**協作式**多工。 再以 A、B 函式為例, A 可以一直執行到送出網頁請求, 開始等待網頁送達時主動釋出執行權讓 B 執行, 像是這樣: ``` A B | | ]- A 送出網頁請求後主動釋出執行權 = | ]- 換 B 接續執行 | | | | | ]- B 執行完畢, 換回 A 繼續 ``` 在 Python 中是由 [asyncio](https://docs.python.org/3/library/asyncio.html) 模組以多個[**協程 (coroutine)**](https://docs.python.org/3/glossary.html#term-coroutine) 物件提供協作式多工的機制, 搭配各種輸出入模組, 達成非同步輸出入的功能。 ## 平行處理 (parellel processing) 你可能已經想到, 如果有兩台遊戲機, 那不就兩個人都可以同時玩, 不需要等來等去?沒錯, 這樣的方式稱為**平行處理**, 也就是兩個人玩遊戲機的時間可以完全重疊, 而不是互相輪替。平行處理是並行處理的一種方式, 可以達到真正同時執行的多工。如果電腦上有多個處理器或核心, 就可以讓 A、B 函式分別在不同的處理器上同時執行, 像是這樣: ``` A B | | | | = | | | | ``` 即使 A 函式在第三個時段是在空等, 但是因為 B 是在另外一個處理器上執行, 所以不需要浪費時間等待。 在 Python 中是由 [multiprocessing](https://docs.python.org/3/library/multiprocessing.html) 模組以多個**行程 (process)** 物件提供平行處理的機制。 整體來說, 多緒程或是多協程比較適合大量輸出入並且需要等待傳輸完成的程式;而多行程比較適合需要大量運算, 沒有等待輸出入完成的程式。