43. 使用多進程

由於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密集型的任務,通過多進程來處理比多線程更好。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章