由於Python中全局解釋器鎖(GIL)的存在,在任意時刻只允許一個線程在解釋器中運行,因此Python的多線程不適合處理CPU密集型的任務。
要求:想要處理CPU密集型的任務,可以使用多進程模型。
解決方案:
使用標準庫中multiprocessing.Process
類,它可以確定子進程執行任務。操作接口、進程間通信、進程加同步等都與threading.Thread
類類似。
- 對於
multiprocessing.Process
類:
在multiprocessing中,通過創建一個Process對象然後調用它的start()
方法來生成進程。 Process和threading.Thread
的API 相同,start()
、join()
和run()
方法作用一致。
進程和線程的根本區別:進程是操作系統資源分配的基本單位,而線程是任務調度和執行的基本單位。
進程是線程的容器,不存在沒有線程的進程。如果一個進程內有多個線程,則執行過程不是一條線的,而是多條線(線程)共同完成的;線程是進程的一部分,所以線程也被稱爲輕權進程或者輕量級進程。
進程有自己獨立的地址空間,每啓動一個進程,系統都會爲其分配地址空間,建立數據表來維護代碼段、堆棧段和數據段,線程沒有獨立的地址空間,它使用相同的地址空間共享數據。
- 對於進程間通信:
隊列:
線程間通信使用的是標準庫中的queue.Queue
類,它是一個線程安全的隊列。而進程間通信使用的是multiprocessing.Queue
類,它是一個近似queue.Queue
的克隆,也是線程和進程安全的。
管道:
Pipe()
函數返回一個由管道連接的連接對象,默認情況下是雙工(雙向)。返回的兩個連接對象Pipe()
表示管道的兩端。每個連接對象都有send()
和recv()
方法(相互之間的)。請注意,如果兩個進程(或線程)同時嘗試讀取或寫入管道的同一端,則管道中的數據可能會損壞。當然,同時使用管道的不同端的進程不存在損壞的風險。
>>> from multiprocessing import Process, Pipe
>>> c1, c2 = Pipe()
>>> def f(c):
... print('in child')
... data = c.recv()
... print(data)
... c.send(data * 2)
...
>>> Process(target=f, args=(c2,)).start()
in child
>>> c1.send(100)
100
>>> c1.recv()
200
send()
方法表示輸入數據到管道;recv()
方法表示從管道中接收數據。
- 方案示例:
import time
from threading import Thread
from multiprocessing import Process
from queue import Queue as Thread_Queue
from multiprocessing import Queue as Process_Queue
def is_armstrong(n): #判斷是否是水仙花數
a, t = [], n
while t :
a.append(t % 10)
t //= 10
k = len(a)
return sum(x**k for x in a) == n
def find_armstrong(a, b, q=None): #在(a, b)內找出水仙花數
res = [x for x in range(a, b) if is_armstrong(x)]
if q:
q.put(res)
return res
def find_by_thread(*ranges): #通過線程尋找
q = Thread_Queue()
workers = []
for r in ranges:
a, b = r
t = Thread(target=find_armstrong, args=(a, b, q))
t.start()
workers.append(t)
res = []
for _ in range(len(ranges)):
res.append(q.get())
return res
def find_by_process(*ranges): #通過進程尋找
q = Process_Queue()
workers = []
for r in ranges:
a, b = r
t = Process(target=find_armstrong, args=(a, b, q))
t.start()
workers.append(t)
res = []
for _ in range(len(ranges)):
res.extend(q.get())
return res
if __name__ == '__main__':
t0 = time.time()
res = find_by_thread([10000000, 15000000], [15000000, 20000000], [20000000, 25000000], [25000000, 30000000])
# res = find_by_process([10000000, 15000000], [15000000, 20000000], [20000000, 25000000], [25000000, 30000000])
print(res)
print(time.time() - t0)
[[24678050, 24678051], [], [], []] #find_by_thread 結果
98.16692352294922
[24678050, 24678051] #find_by_process 結果
55.84143948554993
從運行結果來看,對於CPU密集型的任務,通過多進程來處理比多線程更好。