Python的多工程式設計
文章轉載自東凌閣
GoodMai 好買網
--------------------------------------------------------------------------------------------------------------
前言
Python程式程式碼都是按自上而下的順序載入並執行的,但實際需要程式碼處理的任務並不都是需要按部就班的順序執行,通常為提高程式碼執行的效率,需要多個程式碼執行任務同時執行,也就是多工程式設計的需求。
基本的計算機模型是由CPU、RAM及各種資源(鍵盤、硬碟、顯示卡、網路卡等)組成,程式碼的執行過程,實際就是CPU和相關暫存器及RAM之間的相關處理的過程。在單核CPU場景下,一段程式碼交由CPU執行前,都會處於就緒佇列中,CPU執行時很快就會返回該段程式碼的結果,所以不同程式的程式碼是輪流由CPU執行的,由於CPU執行速度很快,在表現上仍會被感覺是同時執行的。不同就緒佇列間的讀入與結果儲存被稱之為上下文切換,由於程式間切換會產生一定的時間等待及資源的消耗,所以為了減少等待時間和資源的消耗,就引入了執行緒的設計。執行緒是當程式的佇列被授權佔用CPU時,該程式的所有執行緒佇列在共享該程式資源的環境下按優先順序由CPU執行。無論是程式還是執行緒,其佇列及資源切換都是由作業系統進行控制的,同時執行緒的切換也是非常消耗效能的,為了使各執行緒的排程更節約資源,就出現了協程的設計。協程是在程式或執行緒環境下執行的,其擁有自己的暫存器上下文和棧,排程是完全由使用者控制的,相當於函式方法的排程。對於多工程式設計,若要實現程式碼的多工高效率執行,我們要明晰如下這幾個概念的特點及其區別,才能根據實際需求,選用最佳的多工程式設計方法。
並行
指在同一時刻有多個程式的指令在多個處理器上同時執行。
併發
是指在同一時刻只能有一個程式的指令執行,但多個程式指令被快速輪換執行,使得在宏觀上具有多個程式同時執行的效果。
程式
程式是程式的執行態,程式間資料共享需要藉助外部儲存空間。
執行緒
執行緒是程式的組成部分,一個程式可以包含一個或多個執行緒,同一程式內執行緒間資料共享屬於內部共享。
協程
協程是一種使用者態的輕量級執行緒,一個程式可以包含一個或多個協程,也可以在一個執行緒包含一個或多個協程。協程的排程完全由使用者控制,同一程式內協程間資料共享屬於內部共享。
多執行緒處理
由於Python是動態編譯的語言,與C/C++、Java等靜態語言不同,它是在執行時一句一句程式碼地邊編譯邊執行的。用C語言實現的Python直譯器,通常稱為CPython,也是Python環境預設的編譯器。在Cpython直譯器中,為防止多個執行緒同時執行同一 Python 的程式碼段,確保執行緒資料安全,引入了全域性直譯器鎖(GIL, Global Interpreter Lock)的處理機制, 該機制相當於一個互斥鎖,所以即便一個程式下開啟了多執行緒,但同一時刻只能有一個執行緒被執行。所以Python 的多執行緒是偽執行緒,效能並不高,也無法利用CPU多核的優勢。
另,GIL並不是Python的特性,他是在實現Python直譯器(Cpython)時所引入的一個概念,GIL保護的是直譯器級的資料,保護使用者自己的資料仍需要自己加鎖處理。在預設情況下,由於GIL的存在,為了使多執行緒(threading)執行效率更高,需要使用join方法對無序的執行緒進行阻塞,如下程式碼可以看到區別。
```
from multiprocessing import Process
import threading
import os,time
l=[]
stop=time.time()
def work():
global stop
time.sleep(2)
print('===>',threading.current_thread().name)
stop=time.time()
def test1():
for i in range(400):
p=threading.Thread(target=work,name="test"+str(i))
l.append(p)
p.start()
def test2():
for i in range(400):
p=threading.Thread(target=work,name="test"+str(i))
l.append(p)
p.start()
for p in l:
p.join()
if __name__ == '__main__':
print("CPU Core:",os.cpu_count()) #本機為4核
print("Worker: 400") #測試執行緒數
start=time.time()
test1()
active_count=threading.active_count()
while (active_count>1):
active_count=threading.active_count()
continue
test1_result=stop-start
start=time.time()
l=[]
test2()
active_count=threading.active_count()
while (active_count>1):
active_count=threading.active_count()
continue
print('Thread run time is %s' %(test1_result))
print('Thread join run time is %s' %(stop-start))
```
執行結果如下:
```
Thread run time is 4.829492807388306
Thread join run time is 2.053645372390747
```
由上結果可以看到, 多執行緒時join阻塞後執行效率提高了很多。
多程式與多執行緒
多工程式設計的本質是CPU佔用方法的排程處理,對於python下多工處理有多種程式設計方法可供選擇,分別有多程式(multiprocessing)、多執行緒(threading)及非同步協程(Asyncio),在實際使用中該如何選擇呢?我們先看如下一段程式的執行效果。
```
from multiprocessing import Process
from threading import Thread
import os,time
l=[]
def work():
res=0
for i in range(100000000):
res*=i
def test1():
for i in range(4):
p=Process(target=work)
l.append(p)
p.start()
def test2():
for i in range(4):
p=Thread(target=work)
l.append(p)
p.start()
for p in l:
p.join()
if __name__ == '__main__':
print("CPU Core:",os.cpu_count()) #本機為4核
print("Worker: 4") #工作執行緒或子程式數
start=time.time()
test1()
while (l[len(l)-1].is_alive()):
continue
stop=time.time()
print('Process run time is %s' %(stop-start))
start=time.time()
l=[]
test2()
while (l[len(l)-1].is_alive()):
continue
stop=time.time()
print('Thread run time is %s' %(stop-start))
```
執行結果如下:
```
CPU Core: 4
Worker: 4
Process run time is 11.030176877975464
Thread run time is 17.0117769241333
```
從上面的結果,我們可以看到同一個函式用Process及Thread 不同的方法,執行的時間是不同的,為什麼會產生這樣的差異?
多程式(multiprocessing)方法使用子程式而非執行緒,其有效地繞過了全域性直譯器鎖GIL(Global Interpreter Lock), 並充分利用了多核CPU的效能,所以在多核CPU環境下,其比多執行緒方式效率要高。
協程
又稱為微執行緒,協程也可被看作是被標註的函式,不同被表注函式的執行和切換就是協程的切換,其完全由程式設計者自行控制。協程一般是使用 gevent庫,在早期這個庫用起來比較麻煩,所以在python 3.7以後的版本,對協程的使用方法做了最佳化。執行程式碼如下:
```
import asyncio
import time
async def work(i):
await asyncio.sleep(2)
print('===>',i)
async def main():
start=time.time()
l=[]
for i in range(400):
p=asyncio.create_task(work(i))
l.append(p)
for p in l:
await p
stop=time.time()
print('run time is %s' %(stop-start))
asyncio.run(main())
```
執行結果如下:
```
run time is 2.0228068828582764
```
另,預設環境下,協程是在單執行緒模式下執行的非同步操作,其並不能發揮多處理器的效能。為了提升執行效率,可以在多程式中執行協程呼叫方法,程式碼用例如下:
```
from multiprocessing import Process
import asyncio
import os,time
l=[]
async_result=0
async def work1():
res=0
for i in range(100000000):
res*=i
# 協程入口
async def async_test():
m=[]
for i in range(4):
p=asyncio.create_task(work1())
m.append(p)
for p in m:
await p
async def async_test1():
await asyncio.create_task(work1())
def async_run():
asyncio.run(async_test1())
# 多程式入口
def test1():
for i in range(4):
p=Process(target=async_run)
l.append(p)
p.start()
if __name__ == '__main__':
print("CPU Core:",os.cpu_count()) #本機為4核
print("Worker: 4") #工作執行緒或子程式數
start=time.time()
asyncio.run(async_test())
stop=time.time()
print('Asyncio run time is %s' %(stop-start))
start=time.time()
test1()
while (l[len(l)-1].is_alive()):
continue
stop=time.time()
print('Process Asyncio run time is %s' %(stop-start))
```
執行結果如下:
```
CPU Core : 4
Worker : 4
Asyncio run time is 18.89663052558899
Process Asyncio run time is 10.865562438964844
```
如上結果,在多程式中呼叫多協程的方法,執行效率明顯提高。
多工程式設計選擇
如上所結果是否是就決定一定要選擇多程式(multiprocessing)模式呢?我們再看下如下程式碼:
```
from multiprocessing import Process
from threading import Thread
import os,time
l=[]
# 僅計算
def work1():
res=0
for i in range(100000000):
res*=i
# 僅輸出
def work2():
time.sleep(2)
print('===>')
# 多程式,僅計算
def test1():
for i in range(4):
p=Process(target=work1)
l.append(p)
p.start()
# 多程式,僅輸出
def test_1():
for i in range(400):
p=Process(target=work2)
l.append(p)
p.start()
# 多執行緒,僅計算
def test2():
for i in range(4):
p=Thread(target=work1)
l.append(p)
p.start()
for p in l:
p.join()
# 多執行緒,僅輸出
def test_2():
for i in range(400):
p=Thread(target=work2)
l.append(p)
p.start()
for p in l:
p.join()
if __name__ == '__main__':
print("CPU Core:",os.cpu_count()) #本機為4核
start=time.time()
test1()
while (l[len(l)-1].is_alive()):
continue
stop=time.time()
test_result=stop-start
start=time.time()
l=[]
test_1()
while (l[len(l)-1].is_alive()):
continue
stop=time.time()
test1_result=stop-start
start=time.time()
l=[]
test2()
while (l[len(l)-1].is_alive()):
continue
stop=time.time()
test2_result=stop-start
start=time.time()
l=[]
test_2()
while (l[len(l)-1].is_alive()):
continue
stop=time.time()
test3_result=stop-start
print('Process run time is %s' %(test_result))
print('Process I/O run time is %s' %(test1_result))
print('Thread run time is %s' %(test2_result))
print('Thread I/O run time is %s' %(stop-start))
```
執行結果如下:
```
Process run time is 10.77662968635559
Process I/O run time is 2.9869778156280518
Thread run time is 16.842355012893677
Thread I/O run time is 2.024587869644165
```
由結果可看,在僅計算的操作時,多程式效率比較高,在僅輸出的操作時,多執行緒的效率比較高,所以在實際使用中要根據實際情況測試決定。通用的建議如下:
· 多執行緒(threading)用於IO密集型,如socket,爬蟲,web
· 多程式(multiprocessing)用於計算密集型,如資料分析
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70008135/viewspace-2839062/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Python多程式程式設計Python程式設計
- [python] 多程式程式設計Python程式設計
- python中的多工程式設計Python程式設計
- 程式設計師的工資高,到底程式設計師的工資有多高?程式設計師
- python 多執行緒程式設計Python執行緒程式設計
- Python多執行緒程式設計Python執行緒程式設計
- Python多工程式設計介紹Python程式設計
- python 多cpu並行程式設計Python並行行程程式設計
- .NET多執行緒程式設計(1):多工和多執行緒 (轉)執行緒程式設計
- Python多執行緒程式設計(二)Python執行緒程式設計
- Python多程式程式設計基礎——圖文版Python程式設計
- 程式設計師的工資禁忌程式設計師
- python多執行緒程式設計1— python對多執行緒的支援Python執行緒程式設計
- 為什麼程式設計師的工資那麼多!憑什麼?程式設計師
- Python中的多工:多執行緒Python執行緒
- 多程式程式設計 (轉)程式設計
- 多程式程式設計(轉)程式設計
- python的學習(七)----多執行緒程式設計(1)Python執行緒程式設計
- C++多工程式設計簡明教程(1)-C++的多工其實很簡單C++程式設計
- 【Python設計模式】03 工廠模式:建立建立物件的工廠Python設計模式物件
- Python並行程式設計(七):多程式的基本使用和與多執行緒的差異Python並行行程程式設計執行緒
- PHP多程式程式設計(3):多程式抓取網頁的演示PHP程式設計網頁
- python設計模式-工廠方法模式Python設計模式
- python設計模式之工廠模式Python設計模式
- 通俗 Python 設計模式——工廠模式Python設計模式
- python設計模式-抽象工廠模式Python設計模式抽象
- PHP多程式程式設計(一)PHP程式設計
- 程式設計師的晉級之路:程式設計師如何快速工資翻倍?程式設計師
- Python 程式設計師容易忽略的程式設計方式Python程式設計師
- Python程式設計:探索有趣的程式碼設計模式Python程式設計設計模式
- (整合)Linux下的多程式程式設計Linux程式設計
- Python多程序中並行程式設計與程序池Python並行行程程式設計
- 工資低的.Net程式設計師,活該你工資低程式設計師
- python程式設計Python程式設計
- Python 程式設計Python程式設計
- python多工抓取圖片Python
- 為什麼程式設計師工資比其他行業高這麼多?程式設計師行業
- 秋招季,用Python分析深圳程式設計師工資有多高?Python程式設計師