今天來簡單講講多線程(MultiThreading)和多進程(MultiProcessing)。
首先我們要先來理解一下兩者的差異。
線程(Threading)和進程(Processing)的區別:
線程是第二小的執行單元,一個進程可以包含多個線程。線程屬於進程。而協程屬於線程。
進程有自身的地址空間,每個進程都擁有獨立的代碼段、數據段等資源。線程不能脫離進程單獨存在。
線程調度由程序自行控制,進程調度由操作系統Kernel控制。
線程切換消耗資源較小,進程切換需要內核態和用戶態之間的切換,消耗更大。
同一進程內的線程可以共享進程資源,使用簡單的方法互相通信。進程間通信需要IPC。
線程更適合頻繁的通信交互,進程適合較為獨立的子任務。
一個進程內的多個線程可以利用多核,但同一進程最多只能用佔用一個核。
多個進程可以分配給多個內核,有利於提高多核CPU的利用率。
總體來說,線程與進程的選擇需要根據具體場景需求來確定。對於頻繁交互的任務,線程更有優勢。而對於獨立分散的任務,則應優先考慮進程。
接著來簡單說一下使用方法。
Python中的多線程是通過threading模塊實現的。要使用多線程,需要先導入該模塊:
import threading
然後可以通過以下兩種方式創建線程:
t = threading.Thread(target=函數名)
class MyThread(threading.Thread):
def run(self):
# 線程要執行的程式碼
創建線程後,調用start()方法啟動線程:
t = threading.Thread(target=函數名)
t.start()
如果要等待線程結束後再繼續執行,可以調用join()方法。
t.join()
Python中的每啟動一個線程就是啟動一個獨立執行單元。多線程能提高程序運行效率,在一個線程等待時,可以讓其他線程繼續運行。
需要注意的是,Python中的多線程由於全局解釋鎖的存在,不能真正利用多核CPU。如果要發揮多核效能,需要使用多進程。
Python如果要發揮多核CPU的效能,可以通過multiprocessing模塊實現多進程。
使用multiprocessing模塊的主要步驟是:
import multiprocessing
def 函數名(args):
// 要執行的程式碼
p = multiprocessing.Process(target=函數名, args=(args,))
p.start()
p.join()
與threading不同,multiprocessing會創建實際的子進程,可以真正利用多核。
multiprocessing提供了多種類型的進程,如Pool可以創建進程池並發送任務,Queue可以用於進程間通信。
通過合理配置多進程,指派給不同CPU核,可以大大提高Python程序的執行效率。
而使用線程與進程時也有需要注意的地方。
使用線程時,主要注意以下幾點:
多個線程同時對一個對象進行操作時,需要確保代碼是線程安全的,通常可以使用互斥鎖來保證。
需要線程之間協調運行順序,可以使用Event、Condition等方式實現線程同步。
線程間互相持有資源而造成的死鎖問題。可以設計好資源訪問順序來避免。
線程間共享資源的時候一定要確保對資源的訪問是互斥的。
線程間一般通過Queue、Pipe等方式通信傳遞數據。
需要正確處理線程的退出,避免資源洩露等問題。
多線程程序極易出現Heisenbug,需要充分的測試。
每個進程可創建的線程數是有限制的,需要合理規劃。
避免不必要的線程切換影響性能。可以根據需求適當使用線程池。
總之,多線程編程一定要全面考慮各方面因素,謹慎設計和測試,以獲得一個健壯的多線程程序。
進程需要注意的地方也有些類似。
使用進程時,主要需要注意以下幾點:
進程間存在獨立的地址空間,需要使用IPC機制進行通信和數據共享,如管道、信號、Socket等。
進程間數據共享可以使用共享內存,但需要處理好同步問題。也可以使用Manager。
需要進程間協調運行順序,可以使用多種同步原語如互斥鎖、條件變量等。
進程間也存在資源競爭導致死鎖的問題,需要小心避免。
進程執行由操作系統調度,對進程優先級和時間片長度等要有充分考慮。
多個進程共享訪問資源時可能出現競爭條件,需要使用互斥、同步等機制。
關鍵區域的代碼要考慮重入問題,保證進程安全。
進程也有最大數量等資源限制,需要合理分配資源。
追求最大性能時,需要考慮進程創建、上下文切換、IPC通信等開銷。
多進程編程也是非常複雜的,需要全面考慮各方面問題,以實現健壯的多進程協作。
以下是一些解決範例。
在Python的threading模塊中,Lock是一種同步鎖,可以實現對共享資源的互斥訪問,避免數據競爭。 threading.Lock的基本使用方法如下:
lock = threading.Lock()
lock.acquire() # 上鎖
# 訪問共享資源
...
lock.release() # 釋放鎖
with lock:
# 訪問共享資源
...
下面是一個使用Lock的示例:
import threading
import time
lock = threading.Lock()
value = 0
def increment():
global value
with lock:
temp = value + 1
time.sleep(0.1)
value = temp
threads = []
for i in range(10):
t = threading.Thread(target=increment)
t.start()
threads.append(t)
for t in threads:
t.join()
print(value)
上面的代碼會正確打印出10。
需要注意的是,Lock不可以重入,同一線程不能多次獲取同一Lock。
如果需要可重入鎖,可以使用可重入鎖RLock(Reentrant Lock)。
在 Python 的 threading 模組中,lock = threading.Lock()
會建立一個新的鎖物件。lock.acquire()
會嘗試獲取鎖。
如果鎖已經被鎖定,則 acquire()
會阻塞,直到另一個線程調用 release()
將其解鎖。
然後 acquire()
會將其重置為鎖定並返回。release()
方法應僅在鎖定狀態下調用,它將狀態更改為解鎖並立即返回。
這些方法用於確保多個線程之間的同步,並防止多個線程同時訪問共享資源。