Python 正在成為各種應用程序開發(fā)人員中越來越流行的選擇。然而,與任何語言一樣,有效擴展Python 服務可能會帶來挑戰(zhàn)。本文解釋了可用于更好地擴展應用程序的概念。通過了解CPU 密集型任務與 I/O 密集型任務、全局解釋器鎖 (GIL) 的含義以及線程池和 asyncio 背后的機制,我們可以更好地擴展 Python 應用程序。
CPU 限制與 I/O 限制:基礎(chǔ)知識
CPU 密集型任務: 這些任務涉及繁重的計算、數(shù)據(jù)處理和轉(zhuǎn)換,需要大量的 CPU 處理能力。
I/O 密集型任務:這些任務通常等待外部資源,例如讀取或?qū)懭霐?shù)據(jù)庫、文件或網(wǎng)絡(luò)操作。
文章來源地址http://www.zghlxwxcb.cn/article/477.html
識別您的服務主要受 CPU 限制還是 I/O 限制是有效擴展的基礎(chǔ)。
并發(fā)與并行:一個簡單的類比
想象一下計算機上的多任務處理:
并發(fā)性: 您打開了多個應用程序。即使某一時刻只有一個處于活動狀態(tài),您也可以在它們之間快速切換,給人一種它們同時運行的錯覺。
并行性: 多個應用程序真正同時運行,就像在下載文件時在電子表格上運行計算一樣。
在單核CPU場景下,并發(fā)涉及任務的快速切換,而并行則允許多個任務同時執(zhí)行。
全局解釋器鎖:GIL
您可能認為擴展受 CPU 限制的 Python 服務就像添加更多 CPU 能力一樣簡單。然而,Python 標準實現(xiàn)中的全局解釋器鎖 (GIL) 使這一點變得復雜。GIL 是一種互斥體,確保一次只有一個線程執(zhí)行 Python 字節(jié)碼,即使在多核機器上也是如此。此限制意味著 Python 中受 CPU 限制的任務無法充分利用 GIL 的多線程功能。
擴展解決方案:I/O 限制和 CPU 限制
線程池執(zhí)行器
此類提供了使用線程異步執(zhí)行函數(shù)的接口。雖然 Python 中的線程非常適合 I/O 密集型任務(因為它們可以在 I/O 操作期間釋放 GIL),但由于 GIL,它們對于 CPU 密集型任務的效率較低。
異步
asyncio 適合 I/O 密集型任務,為異步 I/O 操作提供事件驅(qū)動框架。它采用單線程模型,在 I/O 等待期間將控制權(quán)交還給事件循環(huán)以執(zhí)行其他任務。與線程相比,asyncio 更精簡,并且避免了線程上下文切換等開銷。
這是一個實際的比較。我們以獲取 URL 數(shù)據(jù)(I/O 綁定)為例,并在沒有線程的情況下使用線程池并使用 asyncio 來完成此操作。
import requests import timeit from concurrent.futures import ThreadPoolExecutor import asyncio URLS = [ "https://www.example.com", "https://www.python.org", "https://www.openai.com", "https://www.github.com" ] * 50 # 獲取URL數(shù)據(jù)的函數(shù) def fetch_url_data(url): response = requests.get(url) return response.text # 1. 順序 def main_sequential(): return [fetch_url_data(url) for url in URLS] # 2. 線程池 def main_threadpool(): with ThreadPoolExecutor(max_workers=4) as executor: return list(executor.map(fetch_url_data, URLS)) # 3. 帶有請求的異步 async def main_asyncio(): loop = asyncio.get_event_loop() futures = [loop.run_in_executor(None, fetch_url_data, url) for url in URLS] return await asyncio.gather(*futures) def run_all_methods_and_time(): methods = [ ("順序", main_sequential), ("線程池", main_threadpool), ("異步", lambda: asyncio.run(main_asyncio())) ] for name, method in methods: start_time = timeit.default_timer() method() elapsed_time = timeit.default_timer() - start_time print(f"{name} 執(zhí)行時間: {elapsed_time:.4f} seconds") if __name__ == "__main__": run_all_methods_and_time()
結(jié)果
順序執(zhí)行時間: 37.9845 seconds
線程池執(zhí)行時間: 13.5944 seconds
異步執(zhí)行時間: 3.4348 seconds
結(jié)果表明,asyncio 對于 I/O 密集型任務非常高效,因為它最小化了開銷并且沒有數(shù)據(jù)同步要求(如多線程所示)。
對于 CPU 密集型任務,請考慮:
多處理:進程不共享 GIL,使得這種方法適合 CPU 密集型任務。但是,請確保生成進程和進程間通信的開銷不會削弱性能優(yōu)勢。
PyPy:帶有即時 (JIT) 編譯器的替代 Python 解釋器。PyPy 可以提高性能,特別是對于 CPU 密集型任務。
在這里,我們有一個正則表達式匹配的示例(CPU 限制)。我們在沒有任何優(yōu)化的情況下使用多處理來實現(xiàn)它。
import re import timeit from multiprocessing import Pool import random import string # 非重復字符的復雜正則表達式模式。 PATTERN_REGEX = r"(?:(\w)(?!.*\1)){10}" def find_pattern(s): """Search for the pattern in a given string and return it, or None if not found.""" match = re.search(PATTERN_REGEX, s) return match.group(0) if match else None # 生成隨機字符串的數(shù)據(jù)集 data = [''.join(random.choices(string.ascii_letters + string.digits, k=1000)) for _ in range(1000)] def concurrent_execution(): with Pool(processes=4) as pool: results = pool.map(find_pattern, data) def sequential_execution(): results = [find_pattern(s) for s in data] if __name__ == "__main__": # Timing both methods concurrent_time = timeit.timeit(concurrent_execution, number=10) sequential_time = timeit.timeit(sequential_execution, number=10) print(f"并發(fā)執(zhí)行時間(多處理): {concurrent_time:.4f} seconds") print(f"順序執(zhí)行時間: {sequential_time:.4f} seconds")
結(jié)果
并發(fā)執(zhí)行時間(多處理): 8.4240 seconds
順序執(zhí)行時間:12.8772 seconds
顯然,多處理優(yōu)于順序執(zhí)行。通過實際用例,結(jié)果將更加明顯。
小結(jié)
擴展 Python 服務取決于識別任務的性質(zhì)(CPU 密集型或 I/O 密集型)并選擇適當?shù)墓ぞ吆筒呗浴τ?I/O 密集型服務,請考慮使用線程池執(zhí)行程序或asyncio,而對于 CPU 密集型服務,請考慮利用多處理。文章來源:http://www.zghlxwxcb.cn/article/477.html
到此這篇關(guān)于如何擴展Python 服務的文章就介紹到這了,更多相關(guān)內(nèi)容可以在右上角搜索或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!