title: Python多執行緒程式設計深度探索:從入門到實戰
date: 2024/4/28 18:57:17
updated: 2024/4/28 18:57:17
categories:
- 後端開發
tags:
- 多執行緒
- 併發程式設計
- 執行緒安全
- Python
- 非同步IO
- 效能最佳化
- 實戰專案
第1章:Python基礎知識與多執行緒概念
Python簡介:
Python是一種高階、通用、解釋型的程式語言,由Guido van Rossum於1991年建立。Python以其簡潔、易讀的語法而聞名,被廣泛用於Web開發、資料科學、人工智慧等領域。Python具有豐富的標準庫和第三方庫,支援多種程式設計正規化,包括物件導向、函式式和程序式程式設計。
執行緒與程序的區別:
- 程序(Process)是作業系統分配資源的基本單位,每個程序有獨立的記憶體空間,程序之間相互獨立。
- 執行緒(Thread)是程序內的執行單元,一個程序可以包含多個執行緒,它們共享程序的記憶體空間和資源。
- 執行緒比程序更輕量級,建立和銷燬執行緒的開銷較小,執行緒間的切換速度也更快。
- 多執行緒程式設計通常用於提高程式的併發性和效率,但也需要注意執行緒安全和同步的問題。
Python中的執行緒支援:
Python標準庫中的threading
模組提供了對執行緒的支援,使得在Python中可以方便地建立和管理執行緒。threading
模組提供了Thread
類用於建立執行緒物件,透過繼承Thread
類並重寫run()
方法可以定義執行緒的執行邏輯。除了基本的執行緒操作外,threading
模組還提供了鎖、事件、條件變數等同步工具,幫助開發者處理執行緒間的同步和通訊問題。在Python中,由於全域性直譯器鎖(GIL)的存在,多執行緒並不能實現真正意義上的並行執行,但可以用於處理I/O密集型任務和提高程式的響應速度。
第2章:Python多執行緒基礎
建立執行緒:threading模組
在Python中,我們可以使用threading
模組來建立和管理執行緒。主要步驟如下:
- 匯入
threading
模組 - 定義一個繼承自
threading.Thread
的子類,並重寫run()
方法來實現執行緒的執行邏輯 - 建立該子類的例項,並呼叫
start()
方法啟動執行緒
示例程式碼:
import threading
class MyThread(threading.Thread):
def run(self):
# 執行緒執行的邏輯
print("This is a new thread.")
# 建立執行緒例項並啟動
t = MyThread()
t.start()
執行緒生命週期
執行緒有以下幾種狀態:
- 初始狀態(New):執行緒物件已建立,但還未啟動
- 就緒狀態(Runnable):執行緒已啟動,正在等待CPU時間片
- 執行狀態(Running):執行緒獲得CPU時間片並正在執行
- 阻塞狀態(Blocked):執行緒由於某種原因放棄CPU時間片,暫時無法執行
- 終止狀態(Terminated):執行緒已經結束執行
執行緒在這些狀態之間轉換,直到最終進入終止狀態。
執行緒同步與通訊
由於執行緒共享程序的資源,因此需要使用同步機制來協調執行緒的訪問,避免出現資料競爭和不一致的問題。threading
模組提供了以下同步工具:
Lock
:互斥鎖,用於保護臨界區資源RLock
:可重入鎖,允許同一執行緒多次獲取鎖Condition
:條件變數,用於執行緒間的通知和等待Semaphore
:訊號量,控制對共享資源的訪問數量Event
:事件物件,用於執行緒間的事件通知
第3章:執行緒池與非同步程式設計
ThreadPoolExecutor
ThreadPoolExecutor
是Python中的執行緒池實現,位於concurrent.futures
模組中,可以方便地管理多個執行緒來執行併發任務。主要特點包括:
- 提供了
submit()
方法來提交任務給執行緒池執行 - 可以控制執行緒池的大小,避免建立過多執行緒導致資源浪費
- 支援非同步獲取任務執行結果
示例程式碼:
from concurrent.futures import ThreadPoolExecutor
def task(n):
return n * n
# 建立執行緒池
with ThreadPoolExecutor(max_workers=3) as executor:
# 提交任務
future = executor.submit(task, 5)
# 獲取任務結果
result = future.result()
print(result)
Asynchronous I/O與協程
非同步I/O是一種非阻塞的I/O模型,透過事件迴圈在I/O操作完成前不斷切換執行任務,提高程式的併發效能。Python中的協程是一種輕量級的執行緒,可以在遇到I/O操作時主動讓出CPU,讓其他任務執行。
asyncio模組簡介
asyncio
是Python標準庫中用於編寫非同步I/O的模組,基於事件迴圈和協程的概念,提供了高效的非同步程式設計解決方案。主要組成部分包括:
- 事件迴圈(Event Loop):負責排程協程任務的執行
- 協程(Coroutines):使用
async
和await
關鍵字定義的非同步任務 - Future物件:表示非同步操作的結果,可用於獲取任務執行狀態和結果
- 非同步I/O操作:透過
asyncio
提供的非同步API實現非阻塞I/O操作
示例程式碼:
import asyncio
async def main():
print("Hello")
await asyncio.sleep(1)
print("World")
# 建立事件迴圈並執行協程
asyncio.run(main())
總結:執行緒池和非同步程式設計是Python中處理併發任務的重要技術,能夠提高程式的效能和效率。透過ThreadPoolExecutor
管理執行緒池,以及利用asyncio
模組實現非同步I/O和協程,可以編寫出高效且響應迅速的非同步程式。
第4章:執行緒同步技術
Locks和RLocks
- Locks(簡單鎖):
threading.Lock
是互斥鎖,用於保護共享資源,確保在一個時間只有一個執行緒可以訪問。當一個執行緒獲取到鎖後,其他執行緒必須等待該鎖釋放。
import threading
lock = threading.Lock()
def thread_function():
with lock:
print("Thread is executing")
- RLocks(可重入鎖,Reentrant Locks):
threading.RLock
允許在已經獲取鎖的執行緒中再次獲取,但不能在其他執行緒中獲取。這在需要在迴圈內部獲取鎖的場景中很有用。
rlock = threading.RLock()
for _ in range(5):
rlock.acquire()
# do something
rlock.release()
Semaphores
- Semaphores(訊號量):
threading.Semaphore
用於控制同時訪問資源的執行緒數量。它維護一個計數器,當計數器大於0時,執行緒可以獲取,計數器減一;當計數器為0時,執行緒必須等待。
semaphore = threading.Semaphore(3)
def thread_function():
semaphore.acquire()
try:
# do something
finally:
semaphore.release()
Conditions and Events
- Conditions(條件變數):
threading.Condition
用於執行緒之間的通訊,允許執行緒在滿足特定條件時進入或退出等待狀態。它通常與鎖一起使用。
lock = threading.Lock()
cond = threading.Condition(lock)
def thread1():
cond.acquire()
try:
# wait for condition
cond.wait()
# do something
finally:
cond.release()
def thread2():
with lock:
# set condition
cond.notify_all()
- Events(事件):
threading.Event
也用於執行緒間的通訊,但它只是標誌,可以被設定或清除。當設定後,所有等待的執行緒都會被喚醒。
event = threading.Event()
def thread1():
event.wait() # 等待事件
# do something
event.set() # 設定事件,喚醒等待的執行緒
Queues和Priority Queues
- Queues(佇列):
queue
模組提供了多種佇列實現,如Queue
、PriorityQueue
等。Queue
是FIFO(先進先出)佇列,PriorityQueue
是優先順序佇列,按照元素的優先順序進行排序。
import queue
q = queue.Queue()
q.put('A')
q.put('B')
q.get() # 返回'A'
q.put('C', block=False) # 如果佇列滿,不阻塞,直接丟擲異常
# 使用PriorityQueue
pq = queue.PriorityQueue()
pq.put((3, 'C'))
pq.put((1, 'A'))
pq.get() # 返回('A', 1)
這些同步工具幫助管理執行緒間的互動,確保資源安全和併發控制。在併發程式設計中,正確使用這些技術是避免競態條件和死鎖的關鍵。
第5章:執行緒間的通訊與資料共享
Shared Memory
- 共享記憶體是執行緒間通訊的一種方式。Python中可以使用
multiprocessing
模組中的Value
和Array
來建立共享記憶體物件。
from multiprocessing import Value, Array
def worker(counter, array):
with counter.get_lock():
counter.value += 1
array[0] += 1
if __:
counter = Value('i', 0) # 'i'表示整型
array = Array('i', 3) # 長度為3的整型陣列
# 多個執行緒可以訪問counter和array
Pickle和Queue模組
- Pickle模組可以將Python物件序列化為位元組流,線上程間傳遞。
- Queue模組提供了執行緒安全的佇列實現,可以用於執行緒間通訊。
import pickle
from queue import Queue
q = Queue()
obj = {'a': 1, 'b': 2}
q.put(pickle.dumps(obj))
received_obj = pickle.loads(q.get())
threading.local
- threading.local可以為每個執行緒建立獨立的資料副本。這對於需要線上程間共享資料但又不希望產生競爭條件的情況很有用。
import threading
local_data = threading.local()
def worker():
local_data.x = 123
print(f"Thread {threading.current_thread().name}: {local_data.x}")
if __:
t1 = threading.Thread(target=worker)
t2 = threading.Thread(target=worker)
t1.start()
t2.start()
t1.join()
t2.join()
這些通訊和共享技術可以幫助我們在多執行緒環境中更好地管理資料和狀態。合理使用這些工具可以提高程式的併發性和健壯性。
第6章:執行緒安全與併發程式設計最佳實踐
避免全域性變數的使用
- 全域性變數在多執行緒環境下容易產生競爭條件和執行緒安全問題。
- 應儘量使用區域性變數或將共享資料封裝到物件中。如果必須使用全域性變數,要對其進行加鎖保護。
避免死鎖
-
死鎖是多執行緒程式設計中常見的問題。產生死鎖的主要原因包括:
- 迴圈等待資源
- 資源佔用和請求不當
- 資源分配策略不當
-
預防死鎖的措施包括:
- 合理設計資源分配策略
- 使用順序加鎖
- 使用超時機制
- 使用
threading.RLock
支援重入
使用執行緒池的注意事項
-
執行緒池可以幫助管理執行緒的建立和銷燬,提高效能。但使用時需注意:
- 執行緒池大小設定要合理,既不能過小影響併發度,也不能過大耗費資源
- 任務提交要合理安排,避免短時間內大量任務堆積
- 合理設定任務超時時間,避免無法響應的任務阻塞執行緒池
- 監控執行緒池健康狀態,及時處理異常情況
第7章:併發程式設計實戰專案
網路爬蟲併發處理
-
網路爬蟲是常見的併發程式設計應用場景。可以使用多執行緒技術併發處理多個URL,提高爬取速度。
- 使用執行緒池管理工作執行緒,提交爬取任務。
- 使用
concurrent.futures
模組提交I/O密集型任務。 - 使用
queue.Queue
或collections.deque
管理URL佇列,避免爬取重複頁面。 - 使用
threading.Semaphore
限制併發數量,避免爬取速度過快被伺服器拒絕。
資料分析任務並行處理
-
資料分析任務也可以使用多執行緒技術提高處理速度。
- 使用
concurrent.futures
模組提交CPU密集型任務。 - 使用
multiprocessing
模組提交CPU密集型任務,避免GIL的限制。 - 使用
Pool.map
或Pool.starmap
分發資料,使用Pool.apply
或Pool.apply_async
分發函式。 - 使用
concurrent.futures
模組的ThreadPoolExecutor
和ProcessPoolExecutor
兩種模式,選擇適合的併發模型。
- 使用
GUI應用中的多執行緒
-
GUI應用中使用多執行緒需要注意:
- GUI執行緒必須獨立,不能被其他執行緒阻塞。
- 資料共享需要使用佇列或管道,避免直接修改GUI控制元件。
- 使用
threading.Event
或threading.Condition
實現執行緒間通訊。 - 使用
QThread
和QRunnable
等Qt提供的多執行緒工具。
總之,在實際專案中,需要根據具體情況合理使用併發程式設計技術,提高系統效能和效率。同時,需要注意執行緒安全和可維護性問題,避免過度使用多執行緒帶來的複雜性。
第8章:多執行緒在分散式系統中的應用
遠端過程呼叫(RPC, Remote Procedure Call)
-
RPC是一種允許分散式系統中的應用程序之間互相呼叫對方的程式功能的技術。
-
使用多執行緒的RPC可以實現:
- 在伺服器端,每個處理執行緒處理客戶端的請求,提高併發能力。
- 在客戶端,發起請求和接收回應可以非同步進行,提高響應速度。
- 使用如
gRPC
、SOAP
、RESTful API
等技術實現,如gRPC
使用protobuf
定義服務和訊息,threading
或asyncio
處理請求。
-
Socket多執行緒伺服器實現
-
Socket多執行緒伺服器是分散式系統中常見的伺服器架構,適用於網路通訊場景。
-
實現步驟:
- 建立一個主執行緒,監聽指定的埠,接受客戶端連線。
- 使用
socket.accept()
建立新的子執行緒(客戶端連線)。 - 每個子執行緒(伺服器端)建立一個單獨的執行緒處理客戶端請求,如讀取資料、傳送資料,可以使用
socket.recv()
和socket.send()
。 - 確保子執行緒在完成任務後正確關閉連線,如使用
socket.close()
。 - 使用
threading.Thread
或asyncio
的start_server
函式來實現多執行緒服務。
import socket
import threading
def handle_client(client_socket):
request = client_socket.recv(1024)
# 處理請求
response = "Hello, Client!"
client_socket.send(response.encode())
client_socket.close()
def server_thread(host, port):
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((host, port))
server_socket.listen(5)
while True:
client, addr = server_socket.accept()
client_handler = threading.Thread(target=handle_client, args=(client,))
client_handler.start()
if __name__ == "__main__":
server_thread('localhost', 12345)
這個例子展示瞭如何建立一個基本的Socket多執行緒伺服器。在實際專案中,可能還需要處理異常、連線管理、負載均衡等複雜情況。
第9章:執行緒安全的併發資料結構
在多執行緒程式設計中,使用執行緒安全的資料結構可以確保在多個執行緒中進行讀寫操作時不會發生競爭條件和資料不一致。
collections.deque
: 一個執行緒安全的雙端佇列,可以用於多執行緒環境下的佇列操作。queue.Queue
: 一個基於鎖的佇列,可以用於多執行緒環境下的生產者-消費者模型。threading.Semaphore
: 一個計數訊號量,可以用於對有限資源進行訪問控制。threading.Lock
: 一個基本的互斥鎖,可以用於對共享資源進行訪問控制。threading.RLock
: 一個可重入的互斥鎖,可以用於對共享資源進行訪問控制。
concurrent.futures模組
concurrent.futures
是一個高階併發庫,提供了一種簡單的方式來使用多執行緒和多程序。ThreadPoolExecutor
: 一個基於執行緒池的執行器,可以用於在多執行緒中執行任務。ProcessPoolExecutor
: 一個基於程序池的執行器,可以用於在多程序中執行任務。Future
: 一個可以在未來返回結果的物件,可以用於在多執行緒和多程序中執行任務。
threading.local的高階應用
threading.local
: 一個執行緒本地儲存物件,可以用於在多執行緒中儲存執行緒特定的資料。- 高階應用:可以用於在多執行緒中實現執行緒隔離的資料庫連線池。
import threading
class ThreadLocalDBConnection:
_instances = {}
def __init__(self, db_name):
self.db_name = db_name
def __enter__(self):
if self.db_name not in self._instances:
self._instances[self.db_name] = threading.local()
self._instances[self.db_name].conn = create_connection(self.db_name)
return self._instances[self.db_name].conn
def __exit__(self, exc_type, exc_val, exc_tb):
self._instances[self.db_name].conn.close()
# 使用
with ThreadLocalDBConnection('db1') as conn:
# 在當前執行緒中使用conn
這個例子展示瞭如何使用threading.local
實現一個執行緒隔離的資料庫連線池。在多執行緒中使用它,可以確保每個執行緒都有自己的連線,而不會發生競爭條件。
第10章:效能調優與執行緒管理
執行緒效能瓶頸分析
- CPU密集型:當程式的瓶頸在CPU上時,可以透過使用多執行緒或多程序來提高效能。
- I/O密集型:當程式的瓶頸在I/O上時,可以使用多執行緒來提高效能。
- 鎖競爭:當多個執行緒在爭搶同一個鎖時,可能會導致效能瓶頸。
- 死鎖:當多個執行緒因爭搶資源而導致死鎖時,可能會導致效能瓶頸。
執行緒池大小的最佳化
- 執行緒數量與CPU核心數量相等:在CPU密集型的程式中,可以將執行緒數量設為CPU核心數量。
- 執行緒數量與CPU核心數量的兩倍:在I/O密集型的程式中,可以將執行緒數量設為CPU核心數量的兩倍。
- 執行緒數量與系統資源有關:在系統資源有限的情況下,可以適當減小執行緒數量。
執行緒生命週期管理
- 執行緒建立:建立一個執行緒需要消耗一定的系統資源。
- 執行緒啟動:啟動一個執行緒需要消耗一定的系統資源。
- 執行緒執行:執行緒執行期間需要消耗CPU資源。
- 執行緒結束:結束一個執行緒需要消耗一定的系統資源。
在管理執行緒生命週期時,可以採用如下策略:
- 預先建立執行緒:在程式啟動時,預先建立一定數量的執行緒,並將它們放入執行緒池中。
- 按需建立執行緒:在程式執行時,按需建立執行緒,並將它們放入執行緒池中。
- 限制執行緒數量:在程式執行時,限制執行緒數量,避免建立過多的執行緒導致系統資源不足。
import threading
import time
class MyThread(threading.Thread):
def run(self):
time.sleep(1)
# 預先建立執行緒
thread_pool = [MyThread() for _ in range(10)]
for thread in thread_pool:
thread.start()
for thread in thread_pool:
thread.join()
# 按需建立執行緒
while True:
if condition:
thread = MyThread()
thread.start()
thread.join()
# 限制執行緒數量
thread_pool = []
for _ in range(10):
thread = MyThread()
thread.start()
thread_pool.append(thread)
for thread in thread_pool:
thread.join()
這些例子展示瞭如何在程式中管理執行緒的生命週期。可以根據實際需求來選擇適合的策略。
第11章:現代Python併發框架:asyncio和AIOHTTP
非同步程式設計的未來
-
Python 3.5引入了asyncio庫,標誌著Python開始支援非同步/協程程式設計,這是一種處理I/O密集型任務的高效方式,尤其是在網路程式設計中。
-
非同步程式設計在未來的發展趨勢:
- 更廣泛的應用:隨著伺服器端和客戶端程式設計的不斷髮展,非同步程式設計將越來越重要,特別是在Web開發、網路服務、遊戲開發等領域。
- 更好的效能:非同步程式設計可以顯著減少阻塞,提高程式的併發處理能力。
- 非同步/並行混合:現代程式設計可能更多地採用非同步I/O與平行計算的結合,以充分利用多核處理器和網路資源。
AIOHTTP庫簡介
- AIOHTTP(Asynchronous I/O HTTP Client/Server)是一個基於asyncio的高效能Python HTTP客戶端和伺服器庫。
- 它的設計目標是提供一個易於使用的API,同時保持高效能和可擴充套件性,特別適合用於構建非同步的Web服務和API。
- AIOHTTP支援HTTP/1.1和HTTP/2協議,支援連線池、請求/響應快取、自動重試、流處理、WebSocket等特性。
- 使用AIOHTTP,開發者可以編寫更簡潔、高效的網路程式碼,減少阻塞,提高併發處理能力。
以下是一個簡單的AIOHTTP示例,用於傳送GET請求:
import asyncio
import aiohttp
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
html = await fetch(session, 'https://example.com')
print(html)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
在這個例子中,fetch
函式是一個協程,使用aiohttp.ClientSession
的非同步上下文管理器來發起GET請求。main
函式也是協程,使用run_until_complete
來排程和執行協程。
AIOHTTP的使用可以幫助你構建更現代、高效的網路應用,尤其是在處理大量併發請求時。
第12章:實戰案例與專案搭建
實戰案例分析
在實際應用中,我們可能需要使用多執行緒爬蟲來抓取大量資料,並對其進行實時分析。這種應用場景可以幫助我們理解如何使用多執行緒技術與資料分析工具來構建一個高效的資料處理系統。
專案實戰:多執行緒爬蟲與實時分析
這個專案將包括以下步驟:
- 確定爬取目標:首先,我們需要確定我們想要爬取的資料。在這個例子中,我們選擇爬取一些新聞網站的文章標題和摘要。
- 設計資料結構:我們需要設計一個資料結構來儲存爬取到的資料。可以使用一個Python字典,包括以下屬性:
title
、summary
、url
。 - 實現多執行緒爬蟲:我們可以使用
concurrent.futures
庫中的ThreadPoolExecutor
來實現多執行緒爬蟲。每個執行緒負責爬取一個網站,並將資料存入一個共享的佇列中。 - 實現實時分析:我們可以使用
pandas
庫來實現資料分析。每當爬蟲從佇列中取出一個新的資料項時,我們可以將其新增到一個pandas.DataFrame
中,並進行實時分析。
以下是一個簡化版的示例程式碼:
import requests
from bs4 import BeautifulSoup
import concurrent.futures
import pandas as pd
# 定義爬取函式
def fetch(url):
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
title = soup.find('h1').text
summary = soup.find('p').text
return {'title': title, 'summary': summary, 'url': url}
# 定義執行緒池
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
# 提交爬取任務
urls = ['https://www.example1.com', 'https://www.example2.com', 'https://www.example3.com']
futures = [executor.submit(fetch, url) for url in urls]
# 獲取爬取結果
data = []
for future in concurrent.futures.as_completed(futures):
result = future.result()
data.append(result)
# 實現實時分析
df = pd.DataFrame(data)
print(df)
在這個示例程式碼中,我們使用ThreadPoolExecutor
來建立一個五個執行緒的執行緒池,並提交三個爬取任務。每個爬取任務負責爬取一個網站,並將資料存入一個列表中。最後,我們將列表轉換為一個pandas.DataFrame
,並進行實時分析。
注意,這個示例程式碼僅供參考,並且可能需要進行修改和最佳化,以適應實際應用場景。
附錄:工具與資源
個人頁面-愛漫畫
相關Python庫介紹
- requests:用於傳送HTTP請求,獲取網頁內容。
- BeautifulSoup:用於解析HTML和XML文件,方便提取資料。
- concurrent.futures:Python標準庫,提供多執行緒和多程序的併發執行框架,如
ThreadPoolExecutor
和ProcessPoolExecutor
。 - pandas:強大的資料處理庫,可以進行資料清洗、轉換、分析等操作。
- threading:Python的內建庫,提供執行緒的基本操作。
- time:用於時間操作,如設定執行緒等待時間。
- logging:用於日誌記錄,便於除錯。
測試與除錯工具
- pytest:Python的測試框架,用於編寫和執行測試用例。
- pdb:Python的內建偵錯程式,用於單步執行程式碼和檢查變數值。
- PyCharm 或
VS Code
:整合開發環境(IDE),有強大的除錯功能。 - Postman 或
curl
:用於測試HTTP請求,確認爬蟲是否正確工作。
高階併發程式設計書籍推薦
- 《Python併發程式設計實戰》(Fluent Python Concurrency) :作者是Luciano Ramalho,深入講解了Python的併發程式設計,包括多執行緒、多程序、協程和非同步I/O等。
- 《Concurrent Programming in Python》(Python併發程式設計) :作者是David Beazley和Brian K. Jones,詳細介紹了Python的併發程式設計技術。
- 《Python Cookbook》(Python程式設計:從入門到實踐) :其中包含了一些高階併發程式設計的實用技巧和示例。
- 《The Art of Multiprocessing》(多執行緒程式設計藝術) :雖然不是專門針對Python,但其原理和策略對理解Python併發程式設計有幫助。
閱讀這些書籍或教程,可以幫助你更好地理解和掌握Python中的併發程式設計,以及如何有效地進行測試和除錯。