---
### [python - speed up]
- [[python - speed up]利用Concurrency加速你的 Python 程式執行效率](https://hackmd.io/@YungHuiHsu/SJ5EgB5eT)
- [[python - speed up] process(程序/進程) 與 thread (執行緒/線程)](https://hackmd.io/@YungHuiHsu/SJCIdO5x6)
- [[python - speed up] 多執行緒(multi-threading)的基本概念](https://hackmd.io/@YungHuiHsu/r1C9Akpgp)
- [python - speed up] threading 模組的使用(待補)
- [[python - speed up] How to speed up multiple camera streams simultaneously with multithreading<br>如何使用多執行序加速視訊(相機/錄影)串流](https://hackmd.io/@YungHuiHsu/BJr5SyYQn)
- [python - speed up] multiprocessing模組的各種使用方法(待補)
- [[python - speed up] multiprocessing模組的Pool功能使用範例。multiprocessing.Pool Sample Notes](https://hackmd.io/@YungHuiHsu/BJiMZLKxp)
---

[Threads and Processes](https://users.cs.cf.ac.uk/dave/C/node29.html)

[javatpoint.com/process-vs-thread](https://www.javatpoint.com/process-vs-thread)
在現代的電腦科學和軟體開發中,善用多核心電腦的 `process` 和 `thread ` 可以達到以下功能:
- 效能提升:透過合理的使用多進程和多執行緒,我們可以讓軟體運行得更快,更有效地利用系統資源。
- 用戶體驗:多進程和多執行緒可以讓軟體在執行耗時的任務時,仍然保持對用戶的響應,例如,可以在一個執行緒下載文件,同時在另一個執行緒繼續瀏覽網頁。
- 解決複雜問題:對於一些複雜的問題,例如大數據分析或複雜的科學計算,多進程和多執行緒提供了一種分而治之的方法,使得問題更易於處理。
一個process就像是一家餐廳。這家餐廳(process)有它自己的資源:廚房(CPU)、食材(記憶體)、廚具(I/O 設備)等等。每一家餐廳都獨立運作,不會影響到其他餐廳。而thread執行緒,則可以想像成是餐廳裡的廚師。一個餐廳(process)可以有一個或多個廚師(執行緒):
單執行緒(single thread):就像一家只有一個廚師的餐廳。無論多少訂單(任務),都只能由這一個廚師依序製作。如果這個廚師在煮一道需要長時間等待的菜(例如:慢燉牛肉),那麼其他的訂單就必須等待。
多執行緒(multi thread):就像一家有多個廚師的餐廳。他們可以同時製作多個訂單,提高效率。但是,如果不協調好,比如說兩個廚師同時使用同一個鍋子(共享資源),就可能會出現問題(競態條件Race Condition)
### 1. 定義、適用任務類型、具體情境、優缺點
| 差異點 | Process (程序/進程) | Thread (執行緒/線程) |
|--------|------|--------|
| 定義 | 進程是一個正在執行的程式| 執行緒是進程的一小部分,它可以獨立運行 |
| 是否共享記憶體 | 彼此**不共享**記憶體空間 | 執行緒會**共享**<br>同一進程內的記憶體空間 |
| 作業系統的處理方式 | 作業系統把每個進程<br>當作一個單獨的個體 | 作業系統把同一進程內<br>的所有執行緒視為一個單位 |
| 阻塞<br>Blocking | 一個進程阻塞不會影響其他進程| 一個執行緒阻塞可能導致同一<br>進程內的所有執行緒都無法進行 |
| 切換速度<br>Context Switching | 相對執行序"重",切換慢| 執行緒之間切換快,因為非常輕量 |
| 資料和程式碼段 | 進程間的資料和程式碼是獨立的 | 執行緒會共享資料和程式碼區塊 |
| 終止時間 | 通常較為耗時 | 可以迅速終止
| 創建時間 |較為耗時,因為每個新進程都需要所有資源。 | 迅速 |
[修改自javatpoint.com/process-vs-thread](https://www.javatpoint.com/process-vs-thread)
- 按照使用情境整理
| 項目 | Process (程序/進程) | Thread (執行緒/線程) |
| ---------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| **定義** | 進程是一個獨立執行的程式實例,擁有自己的記憶體空間。 | 執行緒是進程內的一個單一執行流程,共享該進程的記憶體空間。 |
| **適用任務類型** | 適合CPU密集型(CPU-Bound)任務,或需要獨立記憶體空間的任務。 | 適合I/O密集(I/O-Bound)任務,或需要多任務並行處理的情境。 |
| **具體情境** | - 運行不同的應用程式<br>- 需要隔離記憶體空間的任務 | - GUI 應用程式,需要同時處理用戶輸入和界面更新<br>- 伺服器應用程式,需要同時處理多個客戶端的請求 |
| **優點** | - 較高的記憶體隔離,安全性較高<br>- 進程間不會互相影響 | - 執行緒間資料共享較為方便<br>- 創建和切換執行緒的成本較低 |
| **缺點** | - 創建和切換進程的成本較高<br>- 進程間通信 (IPC) 較為複雜 | - 資料共享可能導致同步問題<br>- 過多的執行緒可能導致調度 overhead |
### python multi-thread與 multi-process模擬實測
| n of <br>process<br>/thread | results |
|:---------------------------:|:---------------------------------------------------:|
| 1 |  |
| 4 |  |
| 8 |  |
ps:測試筆電i5-10210U是4核8執行緒,因此分別用n=4、8來測試
- 在thread與process 數量跟硬體邏輯核心(執行緒)數量一致時,加速效果最明顯
- 在很單純的模擬情境下,可以見到
- multi-thread在I/O密集(I/O-Bound)任務加速效果好
- multi-process則在CPU密集(CPU-Bound)任務加速效果好
:::warning
這邊選用的範例是很不需任何訊息交換、可以單純呈現thread與process特性的模擬任務,實際表現要根據任務的效能瓶頸與具體執行情境而定
:::
- 模擬I/O密集與CPU密集的任務
- 採用`time.sleep(0.1)`模擬I/O密集任務
- 採用平方運算模擬CPU密集任務
```python!
import time
import threading
import multiprocessing
from multiprocessing import Pool
import requests
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
# I/O-bound task: Simulating a network request with time.sleep()
def simulated_io_task():
# Simulating I/O operation by sleeping for 0.1 seconds
time.sleep(0.1)
# CPU-bound task: Calculating the power of large numbers
def simulated_cpu_task(number=99999):
# Calculating power of a large number, which is CPU-intensive
return number ** number
```
- 測量任務計時
- multi-thread與multi-process的計時,分別使用最簡單的`threading.Thread`與`multiprocessing.Process`,開n個thread與process
- direct則直接把函式重複執行n次後計時
-
:::spoiler code of Timing
```python!
# Timing functions
def time_task(task, method, num_tasks):
# Recording the start time
start_time = time.time()
if method == "threading":
# Creating threads and starting them
threads = [threading.Thread(target=task) for _ in range(num_tasks)]
[t.start() for t in threads]
[t.join() for t in threads] # Waiting for all threads to finish
elif method == "multiprocessing":
# Creating processes and starting them
processes = [multiprocessing.Process(target=task) for _ in range(num_tasks)]
[p.start() for p in processes]
[p.join() for p in processes] # Waiting for all processes to finish
# More Automated Uses
# Note: Ensure that your function `task` accepts an argument, even if it's not used,
# because `pool.map` will pass elements from the iterable (here, `range(num_tasks)`) to it.
#
# with Pool(processes=num_tasks) as pool:
# results = pool.map(task, range(num_tasks))
else: # Direct method
# Directly executing the task without threading or multiprocessing
[task() for _ in range(num_tasks)]
# Recording the end time
end_time = time.time()
# Returning the elapsed time in milliseconds
return (end_time - start_time) * 1000
# Timing data
core = 4
data = {
'Task Type': ['I/O-bound'] * 3 + ['CPU-bound'] * 3,
'Method': ['Direct', 'Multi-threading', 'Multi-processing'] * 2,
'Time (ms)': [
time_task(simulated_io_task, "direct", 8),
time_task(simulated_io_task, "threading", 8),
time_task(simulated_io_task, "multiprocessing", 8),
time_task(simulated_cpu_task, "direct", 8),
time_task(simulated_cpu_task, "threading", 8),
time_task(simulated_cpu_task, "multiprocessing", 8)
]
}
```
:::
- 繪圖
:::spoiler code of plot
```python!
# Create a DataFrame
df = pd.DataFrame(data)
# Create Seaborn plots
sns.set_theme(style="whitegrid")
fig, axes = plt.subplots(2, 1, figsize=(6, 4), sharex=True) # Sharing the x-axis
# Define a color palette with soft, pastel colors
palette = sns.color_palette("pastel")
# Subplot for I/O-bound tasks
sns.barplot(ax=axes[0], x='Method', y='Time (ms)', data=df[df['Task Type'] == 'I/O-bound'], palette=palette)
axes[0].set_title(' I/O-bound tasks')
axes[0].set_ylabel('Time (ms)')
# Subplot for CPU-bound tasks
sns.barplot(ax=axes[1], x='Method', y='Time (ms)', data=df[df['Task Type'] == 'CPU-bound'], palette=palette)
axes[1].set_title(' CPU-bound tasks')
axes[1].set_ylabel('Execution Time (ms)')
# Adjusting the layout to prevent overlap
plt.tight_layout()
plt.show()
```
:::
#### 補充
根據python [Concurrent Execution官方文件](https://docs.python.org/zh-tw/3/library/concurrency.html)提到:
:::info
- multiprocessing — Process-based parallelism
- `concurrent.futures.ProcessPoolExecutor` 提供了一個更高層級的介面用來將任務推送到後台進程而**不會阻塞**調用方進程的執行。 與直接使用Pool介面相比,concurrent.futures API 能更好地允許將工作單元發往無需等待結果的下層進程池
- threading — Thread-based parallelism
- `concurrent.futures.ThreadPoolExecutor` 提供了一個高層級介面用來向後台線程推送任務而**不會阻塞**調用方線程的執行,同時仍然能夠在需要時獲取任務的結果。
- `queue` 提供了一個線程安全的介面用來在運行中的線程之間交換數據。
- `asyncio` 提供了一個替代方式用來實現任務層級的併發而不要求使用多個操作系統線程。
詳細操作見[concurrent.futures — Launching parallel tasks](https://docs.python.org/3/library/concurrent.futures.html)一節說明
:::
## Reference
- [geeksforgeeks.org。Multithreading in Python](https://www.geeksforgeeks.org/multithreading-python-set-1/)
- [javatpoint.com。Difference Between Process and Thread](https://www.javatpoint.com/process-vs-thread)
- [2017。louie_lu。深入 GIL: 如何寫出快速且 thread-safe 的 Python - Grok the GIL: How to write fast and thread-safe Python](https://blog.louie.lu/2017/05/19/%E6%B7%B1%E5%85%A5-gil-%E5%A6%82%E4%BD%95%E5%AF%AB%E5%87%BA%E5%BF%AB%E9%80%9F%E4%B8%94-thread-safe-%E7%9A%84-python-grok-the-gil-how-to-write-fast-and-thread-safe-python/)
- [Jim Anderson。realpython.com。An Intro to Threading in Python](https://realpython.com/intro-to-python-threading/)
- [2023。Max行銷誌。【Python教學】淺談 Multi-processing & Multi-threading 使用方法](https://www.maxlist.xyz/2020/03/15/python-threading/)
- [Computational Statistics in Python。Threads_Processses_Concurrency](https://people.duke.edu/~ccc14/sta-663-2016/19B_Threads_Processses_Concurrency.html#Race-conditions)
有關於多核平行運算不錯的範例