Contents

如何透過並發性和平行性增強 Python 程式碼

要點

並發性和平行性代表了計算任務表現的基本原則,每一種都擁有彼此區分的獨特屬性。

並行可以實現資源的有效分配並提高應用程式的回應能力,而平行性在實現峰值效能和擴展能力方面發揮著至關重要的作用。

Python 提供了多種管理並發操作的方法,包括透過其內建線程庫利用線程,以及使用 asyncio 框架支援非同步程式設計。此外,多處理模組允許開發人員在其應用程式中利用並行處理的能力。

並發是指系統同時執行多個進程或執行緒的能力,而並行則是將任務劃分為更小的子任務並由系統的不同部分同時執行的能力。在 Python 中,有許多可用於管理並行性並行性的方法,例如多處理、執行緒、使用 async/await 的非同步程式設計以及使用 Celery 或 Dask 等函式庫進行分散式運算。然而,在決定針對特定情況的最合適方法時,這些選項可能會導致混亂。

深入研究一系列可以有效促進 Python 中並發程式設計技術實現的資源和框架,以及它們之間的差異。

理解並發和並行

並發和並行是描述電腦系統中任務如何執行的兩個重要概念,每個概念都有其獨特的屬性。

⭐ 並發性是程式同時管理多個任務而不必同時執行它們的能力。它圍繞著交錯任務的想法,以一種看似同時的方式在任務之間切換。 /bc/images/task-executed-currently.jpg

⭐ 另一方面,並行性涉及真正並行執行多個任務。它通常利用多個 CPU 核心或處理器。並行實現了真正的同時執行,讓您可以更快地執行任務,並且非常適合計算密集型操作。 /bc/images/task-executed-in-parallel.jpg

並發與並行的重要性

並行和並行處理在計算中的重要性是無可爭議的,因為它使得多個任務能夠同時執行,從而提高效率並減少總體執行時間。由於從科學模擬到業務工作流程自動化等各種應用程式對更快、更有效率的處理能力的需求不斷增長,這種方法變得越來越重要。透過利用多核心處理器和分散式系統的強大功能,並發和平行處理可以提高資源利用率並增強可擴展性,最終帶來更好的效能和更高的生產力。

透過並發可以實現優化的資源分配,因為它可以有效利用系統資產,確保流程繼續有效率地推進,而不是被動地等待外部資源。

增強應用程式回應能力的能力,特別是在使用者介面和 Web 伺服器互動方面,是與並發相關的一個顯著優勢。

透過平行性可以提高效能,特別是在嚴重依賴中央處理單元 (CPU) 的運算任務中,例如複雜的運算、資料操作和建模模擬。

可擴展性是系統設計的關鍵方面,需要並發執行和並行處理才能大規模實現最佳效能。在現代軟體開發中,處理不斷增加的工作負載同時保持效率的能力至關重要。

鑑於硬體技術優先考慮多核心處理能力的新興趨勢,軟體系統必須有效利用並行性以確保其長期生存能力和永續性。

Python 中的並發

在 Python 中,可以透過利用執行緒或非同步技術來實現並發執行,這由 asyncio 函式庫提供了便利。

Python 中的線程

執行緒是 Python 程式設計中的一個固有功能,它允許在統一進程中建立和管理多個並發任務。事實證明,這種機制對於具有大量輸入/輸出操作的任務或那些可能從並行執行中受益的任務特別有利。

Python 的執行緒模組 提供了用於建立和管理執行緒的高階介面。雖然 GIL(全域解釋器鎖定)在真正的並行性方面限制了線程,但它們仍然可以透過有效地交錯任務來實現並發。

提供的程式碼演示了透過在 Python 中使用線程進行並發程式設計的實例。具體來說,它使用Python Request庫來發起HTTP請求,這是一個典型的涉及輸入輸出(I/O)操作並可能導致任務阻塞的操作。此外,程式碼利用時間模組來決定程式執行的持續時間。

 import requests
import time
import threading

urls = [
    'https://www.google.com',
    'https://www.wikipedia.org',
    'https://www.makeuseof.com',
]

# function to request a URL
def download_url(url):
    response = requests.get(url)
    print(f"Downloaded {url}-Status Code: {response.status_code}")


# Execute without threads and measure execution time
start_time = time.time()

for url in urls:
    download_url(url)

end_time = time.time()
print(f"Sequential download took {end_time-start_time:.2f} seconds\n")


# Execute with threads, resetting the time to measure new execution time
start_time = time.time()
threads = []

for url in urls:
    thread = threading.Thread(target=download_url, args=(url,))
    thread.start()
    threads.append(thread)

# Wait for all threads to complete
for thread in threads:
    thread.join()

end_time = time.time()
print(f"Threaded download took {end_time-start_time:.2f} seconds")

事實上,儘管順序執行和平行執行之間存在邊際時間差異,但在利用並發執行緒執行 I/O 密集型操作時,執行該程式顯示出明顯的效率提高。

/bc/images/executing-tasks-sequentially-and-in-threads.jpg

使用 Asyncio 進行非同步編程

asyncio 提供了一個事件循環,用於管理稱為協程的非同步任務。協程是可以暫停和恢復的函數,因此非常適合 I/O 密集型任務。該庫對於任務涉及等待外部資源(例如網路請求)的場景特別有用。

為了使前面的同步請求發送範例適用於 Python 中的非同步編程,您需要進行一些更改。首先,您應該使用非阻塞替代方案,例如“asyncio”和“aiohttp”,而不是使用“requests.get()”和“time.sleep()”(它們是分別暫停執行直到完成或經過時間的阻塞操作) `。這涉及到使用 async def 將現有程式碼包裝在非同步函數中,並用非同步操作取代傳統的 I/O 操作。例如,「 async with aiohttp.ClientSession().post(url) 」可用於非同步傳送 POST 請求,而無需等待回應。此外,錯誤處理和日誌記錄可能還需要調整以適應新的非同步框架。

 import asyncio
import aiohttp
import time

urls = [
    'https://www.google.com',
    'https://www.wikipedia.org',
    'https://www.makeuseof.com',
]

# asynchronous function to request URL
async def download_url(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            content = await response.text()
            print(f"Downloaded {url}-Status Code: {response.status}")

# Main asynchronous function
async def main():
    # Create a list of tasks to download each URL concurrently
    tasks = [download_url(url) for url in urls]

    # Gather and execute the tasks concurrently
    await asyncio.gather(*tasks)

start_time = time.time()

# Run the main asynchronous function
asyncio.run(main())

end_time = time.time()

print(f"Asyncio download took {end_time-start_time:.2f} seconds")

利用所提供的程式碼,人們可以透過利用「asyncio」的功能並利用非同步 I/O 操作的功能來有效地執行多個並發網頁下載。與更適合 CPU 密集型任務的傳統執行緒技術相反,這種方法在優化 I/O 密集型進程方面特別有效。

/bc/images/use-asyncio-to-execute-task-concurrently.jpg

Python 中的平行性

您可以使用Python的多處理模組實現並行性,這使您可以充分利用多核心處理器。

Python 中的多處理

Python 的多處理模組提供了一種透過創建單獨進程來利用並行性的方法,每個進程都配備了獨特的 Python 解釋器和記憶體域。透過這樣做,此方法繞過了 CPU 密集型任務中常見的全域解釋器鎖定 (GIL)。

 import requests
import multiprocessing
import time

urls = [
    'https://www.google.com',
    'https://www.wikipedia.org',
    'https://www.makeuseof.com',
]

# function to request a URL
def download_url(url):
    response = requests.get(url)
    print(f"Downloaded {url}-Status Code: {response.status_code}")

def main():
    # Create a multiprocessing pool with a specified number of processes
    num_processes = len(urls)
    pool = multiprocessing.Pool(processes=num_processes)

    start_time = time.time()
    pool.map(download_url, urls)
    end_time = time.time()

    # Close the pool and wait for all processes to finish
    pool.close()
    pool.join()

    print(f"Multiprocessing download took {end_time-start_time:.2f} seconds")

main()

多處理可以跨多個進程並發執行 download_url 函數,從而促進給定 URL 的平行處理。

/bc/images/execute-task-in-parallel.jpg

何時使用同時或並行

選擇並行處理或並行處理的決定取決於正在執行的操作的特性以及底層系統硬體資源的容量。

在處理以輸入/輸出 (I/O) 為中心的操作(例如文件讀取/寫入操作或網路請求)時,建議使用並發性,因為它能夠同時處理多個任務。此外,它還可以用於記憶體限制構成挑戰的情況。

當使用多處理適合 CPU 密集型操作(可以透過並發執行來增強)時,並且當確保進程的穩健分離是優先事項時,因為每個進程的故障不應過度影響其他進程。

利用並發和平行性

並行和並發執行是提高Python程式效率和吞吐量的可行方法。然而,理解這些技術之間的差異至關重要,以便就哪種方法最適合特定情況做出明智的決定。

Python 提供了一套全面的工具和模組,使開發人員能夠透過利用並發或平行處理技術來提高程式碼效率,無論手頭上的任務主要是計算型任務還是輸入/輸出密集型任務。