Python 中用多線程進行多任務(wù)處理

1. GIL
熟悉python的都知道,在C語言寫的python解釋器中存在全局解釋器鎖,由于全局解釋器鎖的存在,在同一時間內(nèi),python解釋器只能運行一個線程的代碼,這大大影響了python多線程的性能。而這個解釋器鎖由于歷史原因,現(xiàn)在幾乎無法消除。
python GIL 之所以會影響多線程等性能,是因為在多線程的情況下,只有當(dāng)線程獲得了一個全局鎖的時候,那么該線程的代碼才能運行,而全局鎖只有一個,所以使用python多線程,在同一時刻也只有一個線程在運行,因此在即使在多核的情況下也只能發(fā)揮出單核的性能。
2. 多線程處理IO密集型任務(wù)
IO密集型任務(wù)指的是系統(tǒng)的CPU性能相對硬盤、內(nèi)存要好很多,此時,系統(tǒng)運作,大部分的狀況是CPU在等I/O (硬盤/內(nèi)存) 的讀/寫操作,此時CPU Loading并不高。涉及到網(wǎng)絡(luò)、磁盤IO的任務(wù)都是IO密集型任務(wù)。一個線程執(zhí)行IO密集型任務(wù)的時候,CPU處于閑置狀態(tài),因此GIL會被釋放給其他線程,從而縮短了總體的等待運行時間。
from?concurrent.futures?import?ThreadPoolExecutor
from?time?import?sleep,?time
#?Worker數(shù)量
N?=?4
#?建立線程池
pool?=?ThreadPoolExecutor(max_workers=N)
2.1 定義一個IO密集型函數(shù)
該函數(shù)會“睡眠”x秒。
def?io_bound_func(x):
????sleep(x)
????print("Sleep?for?%d?seconds."?%?x)
2.2 使用串行的方式處理
遍歷一個列表的所有元素,執(zhí)行func函數(shù)。
def?process_array(arr):
????for?x?in?arr:
????????io_bound_func(x)
2.3 使用多線程處理
通過線程池的map方法,可以將同一個函數(shù)作用在列表中的所有元素上。
def?fast_process_array(arr):
????for?x?in?pool.map(io_bound_func,?arr):
????????pass
2.4 計算函數(shù)運行時間
串行版本的運行時間 = 1 + 2 + 3 = 6秒 多線程版本的運行時間 = max(1, 2, 3) = 3秒
def?time_it(fn,?*args):
????start?=?time()
????fn(*args)
????print("%s版本的運行時間為?%.5f?秒!"?%?(fn.__name__,?time()?-?start))
time_it(process_array,?[1,?2,?3])
Sleep?for?1?seconds.
Sleep?for?2?seconds.
Sleep?for?3?seconds.
process_array版本的運行時間為?6.00883?秒!
time_it(fast_process_array,?[1,?2,?3])
Sleep?for?1?seconds.
Sleep?for?2?seconds.
Sleep?for?3?seconds.
fast_process_array版本的運行時間為?3.00300?秒!
3. 多線程CPU密集型任務(wù)
CPU密集型任務(wù)的特點是要進行大量的計算,消耗CPU資源,比如計算圓周率、對視頻進行高清解碼等等,全靠CPU的運算能力。一個線程執(zhí)行CPU密集型任務(wù)的時候,CPU處于忙碌狀態(tài),運行1000個字節(jié)碼之后GIL會被釋放給其他線程,加上切換線程的時間有可能會比串行代碼更慢。
3.1 定義一個CPU密集型函數(shù)
該函數(shù)會對[1, x]之間的整數(shù)進行求和。
def?cpu_bound_func(x):
????tot?=?0
????a?=?1
????while?a?<=?x:
????????tot?+=?x
????????a?+=?1
????print("Finish?sum?from?1?to?%d!"?%?x)
????return?tot
3.2 使用串行的方式處理
遍歷一個列表的所有元素,執(zhí)行func函數(shù)。
def?process_array(arr):
????for?x?in?arr:
????????cpu_bound_func(x)
3.3 使用多線程處理
通過線程池的map方法,可以將同一個函數(shù)作用在列表中的所有元素上。
def?fast_process_array(arr):
????for?x?in?pool.map(cpu_bound_func,?arr):
????????pass
3.4 計算函數(shù)運行時間
串行版本的運行時間2.1秒 多線程版本的運行時間2.2秒
def?time_it(fn,?*args):
????start?=?time()
????fn(*args)
????print("%s版本的運行時間為?%.5f?秒!"?%?(fn.__name__,?time()?-?start))
time_it(process_array,?[10**7,?10**7,?10**7])
Finish?sum?from?1?to?10000000!
Finish?sum?from?1?to?10000000!
Finish?sum?from?1?to?10000000!
process_array版本的運行時間為?2.10489?秒!
time_it(fast_process_array,?[10**7,?10**7,?10**7])
Finish?sum?from?1?to?10000000!
Finish?sum?from?1?to?10000000!
Finish?sum?from?1?to?10000000!
fast_process_array版本的運行時間為?2.20897?秒!
參考文章
1、Python中的GIL鎖:https://www.jianshu.com/p/c75ed8a6e9af
2、什么是CPU密集型、IO密集型?:https://www.cnblogs.com/tusheng/articles/10630662.html
作者:李小文,先后從事過數(shù)據(jù)分析、數(shù)據(jù)挖掘工作,主要開發(fā)語言是Python,現(xiàn)任一家小型互聯(lián)網(wǎng)公司的算法工程師。
Github:?https://github.com/tushushu
更多閱讀
特別推薦

點擊下方閱讀原文加入社區(qū)會員
點贊鼓勵一下

