Python 多進程、多線程效率比較

Python 界有條不成文的準則: 計算密集型任務適合多進程,IO 密集型任務適合多線程。本篇來做個比較。python

一般來講多線程相對於多進程有優點,由於建立一個進程開銷比較大,然而由於在 python 中有 GIL 這把大鎖的存在,致使執行計算密集型任務時多線程實際只能是單線程。並且因爲線程之間切換的開銷致使多線程每每比實際的單線程還要慢,因此在 python 中計算密集型任務一般使用多進程,由於各個進程有各自獨立的 GIL,互不干擾。網絡

而在 IO 密集型任務中,CPU 時常處於等待狀態,操做系統須要頻繁與外界環境進行交互,如讀寫文件,在網絡間通訊等。在這期間 GIL 會被釋放,於是就可使用真正的多線程。多線程

以上是理論,下面作一個簡單的模擬測試: 大量計算用 math.sin() + math.cos() 來代替,IO 密集型用 time.sleep() 來模擬。 在 Python 中有多種方式能夠實現多進程和多線程,這裏一併歸入看看是否有效率差別:app

  1. 多進程: joblib.multiprocessing, multiprocessing.Pool, multiprocessing.apply_async, concurrent.futures.ProcessPoolExecutor
  2. 多線程: joblib.threading, threading.Thread, concurrent.futures.ThreadPoolExecutor



from multiprocessing import Pool
from threading import Thread
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time, os, math
from joblib import Parallel, delayed, parallel_backend


def f_IO(a):  # IO 密集型
    time.sleep(5)

def f_compute(a):  # 計算密集型
    for _ in range(int(1e7)):
        math.sin(40) + math.cos(40)
    return

def normal(sub_f):
    for i in range(6):
        sub_f(i)
    return

def joblib_process(sub_f):
    with parallel_backend("multiprocessing", n_jobs=6):
        res = Parallel()(delayed(sub_f)(j) for j in range(6))
    return


def joblib_thread(sub_f):
    with parallel_backend('threading', n_jobs=6):
        res = Parallel()(delayed(sub_f)(j) for j in range(6))
    return

def mp(sub_f):
    with Pool(processes=6) as p:
        res = p.map(sub_f, list(range(6)))
    return

def asy(sub_f):
    with Pool(processes=6) as p:
        result = []
        for j in range(6):
            a = p.apply_async(sub_f, args=(j,))
            result.append(a)
        res = [j.get() for j in result]

def thread(sub_f):
    threads = []
    for j in range(6):
        t = Thread(target=sub_f, args=(j,))
        threads.append(t)
        t.start()
    for t in threads:
        t.join()

def thread_pool(sub_f):
    with ThreadPoolExecutor(max_workers=6) as executor:
        res = [executor.submit(sub_f, j) for j in range(6)]

def process_pool(sub_f):
    with ProcessPoolExecutor(max_workers=6) as executor:
        res = executor.map(sub_f, list(range(6)))

def showtime(f, sub_f, name):
    start_time = time.time()
    f(sub_f)
    print("{} time: {:.4f}s".format(name, time.time() - start_time))

def main(sub_f):
    showtime(normal, sub_f, "normal")
    print()
    print("------ 多進程 ------")
    showtime(joblib_process, sub_f, "joblib multiprocess")
    showtime(mp, sub_f, "pool")
    showtime(asy, sub_f, "async")
    showtime(process_pool, sub_f, "process_pool")
    print()
    print("----- 多線程 -----")
    showtime(joblib_thread, sub_f, "joblib thread")
    showtime(thread, sub_f, "thread")
    showtime(thread_pool, sub_f, "thread_pool")


if __name__ == "__main__":
    print("----- 計算密集型 -----")
    sub_f = f_compute
    main(sub_f)
    print()
    print("----- IO 密集型 -----")
    sub_f = f_IO
    main(sub_f)


結果:async

----- 計算密集型 -----
normal time: 15.1212s

------ 多進程 ------
joblib multiprocess time: 8.2421s
pool time: 8.5439s
async time: 8.3229s
process_pool time: 8.1722s

----- 多線程 -----
joblib thread time: 21.5191s
thread time: 21.3865s
thread_pool time: 22.5104s



----- IO 密集型 -----
normal time: 30.0305s

------ 多進程 ------
joblib multiprocess time: 5.0345s
pool time: 5.0188s
async time: 5.0256s
process_pool time: 5.0263s

----- 多線程 -----
joblib thread time: 5.0142s
thread time: 5.0055s
thread_pool time: 5.0064s


上面每一方法都統一建立6個進程/線程,結果是計算密集型任務中速度:多進程 > 單進程/線程 > 多線程, IO 密集型任務速度: 多線程 > 多進程 > 單進程/線程。測試





/操作系統

相關文章
相關標籤/搜索