Python多進程和多線程測試比高低,只為證明誰是最快的“仔”

目的

前面分別詳細介紹了python的多進程和多線程,如果還沒看前面文章的,請先看下之前的文章詳解內容。有任何疑問請留言。那這裡就不再對多線程和多進程的實現和用法再贅述了。那各位同學學習了python的多線程和多進程,那麼到底是多進程有優勢呢?還是多線程比較快呢?他們兩個哪個運行比較快呢?在代碼編程時到底是用多進程呢,還是用多線程呢,如何最優選擇呢?本篇文章就讓我們討論下python多進程和多線程的應用場景選擇和最優選問題。


Python多進程和多線程測試比高低,只為證明誰是最快的“仔”

python最優選

進程快的佐證測試

當你理解了多線程和多進程後,你是否有過這樣的疑問,它們兩個到底誰更快呢?網上很多都會說那肯定是多進程更快了,因為線程中GIL(全局解釋器鎖)存在,實際上是串行執行的,同一時刻只能有一個線程再跑,而進程是相互獨立,多個進程是同時執行的。那麼,到底是不是這樣的呢?我們通過測試一探究竟。

帶著上面的疑問進行了測試,根據測試結果圖,我們不難看出線程和進程到底哪個快,哪個效率高了。


Python多進程和多線程測試比高低,只為證明誰是最快的“仔”

線程和進程對比圖

那麼根據上圖的測試結果,可以看出進程耗時比較少,線程耗時較多,那說明線程沒有進程快,似乎證實了網上很多人的想法呢。但真是這樣嗎?

線程快的佐證測試

我們都知道:python中的多線程需要先拿到GIL,才能執行代碼,然後釋放GIL。所以由於GIL的存在,實際上它的併發,即多個事件在同一時間是間隔的。而進程有獨立GIL,可以並行實現。因此,針對多核CPU,理論上採用多進程更能有效利用資源,效率更高,耗時更少。但是現實問題是,很多教程中經常能見到python多線程的身影。尤其是:網絡爬蟲、端口掃描等教程中。這是為什麼呢,難道這些教程都是騙人的,寫教程的人都是傻子嗎?顯然不是,認為別人傻的人才是真的傻呢。那麼又是問什麼呢?

這裡舉端口掃描的例子來說吧,代碼如下:

<code>import sys,threadingfrom socket import * host = "127.0.0.1" if len(sys.argv)==1 else sys.argv[1]portList = [i for i in range(1,1000)]scanList = []lock = threading.Lock()print('Please waiting... From ',host)  def scanPort(port):  try:    tcp = socket(AF_INET,SOCK_STREAM)    tcp.connect((host,port))  except:    pass  else:    if lock.acquire():      print('[+]port',port,'open')      lock.release()  finally:    tcp.close() for p in portList:  t = threading.Thread(target=scanPort,args=(p,))  scanList.append(t)for i in range(len(portList)):  scanList[i].start()for i in range(len(portList)):  scanList[i].join()/<code>

測試結果:你會發現python多進程更快些。那麼就是和我們想當然分析得出的結論相悖了嗎?

假設

那這時可能有的同學就懵逼了,那到底是多進程快的還是多線程快呢?這裡,我們根據上面兩個測試不妨大膽假設一下:

CPU密集型場景下,多進程更快,效率更高;而在IO密集型場景下,多線程更有優勢。

為了驗證大家假設,代碼如下:

<code>​import timeimport threadingimport multiprocessing max_process = 4max_thread = max_process def fun(n,n2):  #cpu密集型  for i in range(0,n):    for j in range(0,(int)(n*n*n*n2)):      t = i*j def thread_main(n2):  thread_list = []  for i in range(0,max_thread):    t = threading.Thread(target=fun,args=(50,n2))    thread_list.append(t)   start = time.time()  print(' [+] much thread start')  for i in thread_list:    i.start()  for i in thread_list:    i.join()  print(' [-] much thread use ',time.time()-start,'s') def process_main(n2):  p = multiprocessing.Pool(max_process)  for i in range(0,max_process):    p.apply_async(func = fun,args=(50,n2))  start = time.time()  print(' [+] much process start')  p.close()#關閉進程池  p.join()#等待所有子進程完畢  print(' [-] much process use ',time.time()-start,'s') if __name__=='__main__':  print("[++]When n=50,n2=0.1:")  thread_main(0.1)  process_main(0.1)  print("[++]When n=50,n2=1:")  thread_main(1)  process_main(1)  print("[++]When n=50,n2=10:")  thread_main(10)  process_main(10)/<code>

運行代碼從測試結果如下圖


Python多進程和多線程測試比高低,只為證明誰是最快的“仔”

根據上面的測試圖可以看出,cpu使用率越來越高的時(即代碼循環越多的時),兩者之間的差距越來越大。從而驗證我們的假設

CPU密集型場景下,多進程更快,效率更高;而在IO密集型場景下,多線程更有優勢。

結論

根據上面的測試,我們得到如下結論:

在CPU密集型場景中(如:各種循環處理、計數等等),適合用多進程;

在IO密集型場景中(如:文件處理、網絡爬蟲等),適合用多線程。


方法選擇

那麼有同學看到上面的結論就明白了,多進程和多線程的效率快慢,不是始終不變的,是要應對不通場景的,不一樣的場景,用不同的方法,這才是正確的選擇。那麼cpu密集型場景和io密集型場景我怎麼區分和判斷呢,只有知道當前是何場景,才能做出最優的選擇。下面我就教大家來分辨場景。

簡單的辦法

直接看CPU的佔用率或磁盤的IO讀寫速度。

歸納為:計算較多就是為CPU密集型;時間等待較多(如網絡爬蟲)就是IO密集型。

下面是測試代碼,各位同學不妨自己手動測測,來讓自己有更深層的認識。

<code>#coding=utf-8importsysimportmultiprocessingimporttimeimportthreading​​# 定義全局變量Queueg_queue=multiprocessing.Queue()​definit_queue():print("init g_queue start")whilenotg_queue.empty():g_queue.get()    for_indexinrange(10):g_queue.put(_index)print("init g_queue end")return​# 定義一個IO密集型任務:利用time.sleep()​deftask_io(task_id):print("IOTask[%s] start"%task_id)whilenotg_queue.empty():time.sleep(1)try:data=g_queue.get(block=True, timeout=1)print("IOTask[%s] get data: %s"%(task_id, data))exceptException as excep:print("IOTask[%s] error: %s"%(task_id,str(excep)))print("IOTask[%s] end"%task_id)return​g_search_list=list(range(10000))# 定義一個計算密集型任務:利用一些複雜加減乘除、列表查找等deftask_cpu(task_id):print("CPUTask[%s] start"%task_id)whilenotg_queue.empty():count=0foriinrange(10000):count+=pow(3*2,3*2)ifiing_search_listelse0try:data=g_queue.get(block=True, timeout=1)print("CPUTask[%s] get data: %s"%(task_id, data))exceptException as excep:print("CPUTask[%s] error: %s"%(task_id,str(excep)))print("CPUTask[%s] end"%task_id)returntask_id​if__name__=='__main__':print("cpu count:", multiprocessing.cpu_count(),"\\n")print(u"========== 直接執行IO密集型任務 ==========")init_queue()time_0=time.time()task_io(0)print(u"結束:", time.time()-time_0,"\\n")​print("========== 多線程執行IO密集型任務 ==========")init_queue()time_0=time.time()thread_list=[threading.Thread(target=task_io, args=(i,))foriinrange(10)]​fortinthread_list:t.start()​fortinthread_list:ift.is_alive():t.join()print("結束:", time.time()-time_0,"\\n")​print("========== 多進程執行IO密集型任務 ==========")init_queue()time_0=time.time()process_list=[multiprocessing.Process(target=task_io, args=(i,))foriinrange(multiprocessing.cpu_count())]​forpinprocess_list:p.start()​forpinprocess_list:ifp.is_alive():p.join()print("結束:", time.time()-time_0,"\\n")​print("========== 直接執行CPU密集型任務 ==========")init_queue()time_0=time.time()task_cpu(0)print("結束:", time.time()-time_0,"\\n")​print("========== 多線程執行CPU密集型任務 ==========")init_queue()time_0=time.time()thread_list=[threading.Thread(target=task_cpu, args=(i,))foriinrange(10)]​fortinthread_list:t.start()​fortinthread_list:ift.is_alive():t.join()​print("結束:", time.time()-time_0,"\\n")​print("========== 多進程執行cpu密集型任務 ==========")init_queue()time_0=time.time()process_list=[multiprocessing.Process(target=task_cpu, args=(i,))foriinrange(multiprocessing.cpu_count())]​forpinprocess_list:p.start()​forpinprocess_list:ifp.is_alive():p.join()​print("結束:", time.time()-time_0,"\\n")/<code>

謝謝大家的閱讀,如果你有任何疑問和想法,請關注留言評論,第一時間為你解答心中疑惑。


分享到:


相關文章: