本文以一個簡單的例子介紹python中多線程和多進程的差別。
我們在進行生信分析時經常要處理大文件,如果用串行運算往往費時,所以需要並行運算以節省時間。目前,流行的生信工具通常都可以並行運算,比如bwa。通常來講,我們進行並行運算可以選擇多線程或者多進程。那麼二者有什麼差別呢,我們又該如何選擇呢?
不同編程語言中的多線程和多進程實現機制是不一樣的,其實我們不關心實現機制,我們關注的是實際的性能。本文以python語言爲例,用一個測試腳本來比較python中多線程和多進程的性能區別。我們主要關注運行時間和內存佔用情況。
我們知道,python中常用的多線程模塊是threading,常用的多進程模塊是multiprocessing。我們的測試腳本要解決的是一個運算量比較大的任務,根據是否(並行)運算以及使用哪種並行運算可以分爲四種情形:
- 不進行計算
- 串行運算
- 多線程運算
- 多進程運算
得到的結果如下:
從中可以看出,對這個運算任務以及測試腳本而言,與串行運算相比,多線程所用的時間多很多,所佔的內存一樣;而多進程所用的時間變少(大約是串行運算時間的一半),所佔用的內存變大(大約是串行運算的三倍)。
上述結果值得討論的有兩個:
- 爲什麼python中多線程運算所用的時間比串行運算還多?
這是因爲python中GIL(Global Interpreter Lock)的存在使得對一個進程而言,不管有多少線程,任一時刻,只會有一個線程在執行。對於CPU密集型的線程,由於系統調度等其它時間花銷,其效率不僅僅不高,反而有可能比較低[1]。也就是說,python中的多線程運算不能算作真正的並行運算。上面例子中的任務正好是一個CPU密集型任務,所以用多線程運算的時間反倒比串行運算還多。 - 爲什麼多線程運算佔用的內存和串行運算一樣,而多進程所用內存比串行運算大很多?
這是一個正常的結果,是由線程和進程的特點決定的。簡單來說,線程會共享所屬進程的內存資源,所以不會有額外的內存佔用;而子進程會從父進程那裏拷貝一份內存資源,所以每多一個子進程,就會多一份內存資源的拷貝,佔用的內存就多了,上面的例子中共有兩個子進程,所以就會多出來兩份內存拷貝,看起來所佔用的內存就是串行運算的三倍。(所用的術語只是爲了闡述方便而用,可能有不恰當的地方)
綜上,由於生信分析大多是CPU密集型(計算密集型)的任務,如果你用python來處理此類任務,多進程並行運算可能更適合。
參考
[1] https://www.cnblogs.com/yssjun/p/11302500.html
所用的測試腳本如下:
#!/usr/bin/python
from threading import Thread
from multiprocessing import Process
import time
import sys
def test_func(n):
for _ in range(n):
for i in a:
j = i + 1
print j
a = [1] * 10000000 # 10M
ncore = 2
per_run = 20
total_run = ncore * per_run
if __name__ == "__main__":
start_time = time.time()
if len(sys.argv) < 2:
print "Usage: python %s <no-calc | serial | multi-thread | multi-process>" % sys.argv[0]
sys.exit(1)
cores = None
if sys.argv[1] == "no-calc":
time.sleep(1)
elif sys.argv[1] == "serial":
test_func(total_run)
elif sys.argv[1] == "multi-thread":
cores = [Thread(target = test_func, args=(per_run, )) for _ in range(ncore)]
elif sys.argv[1] == "multi-process":
cores = [Process(target = test_func, args=(per_run, )) for _ in range(ncore)]
else:
print "Usage: python %s <no-calc | serial | multi-thread | multi-process>" % sys.argv[0]
sys.exit(3)
if cores:
for cr in cores:
cr.start()
for cr in cores:
cr.join()
end_time = time.time()
print end_time - start_time