什么是進程進程-操作系統(tǒng)提供的抽象概念,,是系統(tǒng)進行資源分配和調(diào)度的基本單位,,是操作系統(tǒng)結(jié)構(gòu)的基礎(chǔ)。程序是指令,、數(shù)據(jù)及其組織形式的描述,進程是程序的實體,。程序本身是沒有生命周期的,,它只是存在磁盤上的一些指令,程序一旦運行就是進程。 當程序需要運行時,,操作系統(tǒng)將代碼和所有靜態(tài)數(shù)據(jù)記載到內(nèi)存和進程的地址空間(每個進程都擁有唯一的地址空間,,見下圖所示)中,通過創(chuàng)建和初始化棧(局部變量,,函數(shù)參數(shù)和返回地址),、分配堆內(nèi)存以及與IO相關(guān)的任務(wù),當前期準備工作完成,,啟動程序,,OS將CPU的控制權(quán)轉(zhuǎn)移到新創(chuàng)建的進程,進程開始運行,。 操作系統(tǒng)對進程的控制和管理通過PCB(Processing Control Block),,PCB通常是系統(tǒng)內(nèi)存占用區(qū)中的一個連續(xù)存區(qū),它存放著操作系統(tǒng)用于描述進程情況及控制進程運行所需的全部信息(進程標識號,進程狀態(tài),進程優(yōu)先級,文件系統(tǒng)指針以及各個寄存器的內(nèi)容等),,進程的PCB是系統(tǒng)感知進程的唯一實體,。 一個進程至少具有5種基本狀態(tài):初始態(tài)、執(zhí)行狀態(tài),、等待(阻塞)狀態(tài),、就緒狀態(tài)、終止狀態(tài)
進程間的切換無論是在多核還是單核系統(tǒng)中,,一個CPU看上去都像是在并發(fā)的執(zhí)行多個進程,這是通過處理器在進程間切換來實現(xiàn)的。 操作系統(tǒng)對把CPU控制權(quán)在不同進程之間交換執(zhí)行的機制成為上下文切換(context switch),,即保存當前進程的上下文,,恢復新進程的上下文,然后將CPU控制權(quán)轉(zhuǎn)移到新進程,,新進程就會從上次停止的地方開始,。因此,進程是輪流使用CPU的,,CPU被若干進程共享,,使用某種調(diào)度算法來決定何時停止一個進程,并轉(zhuǎn)而為另一個進程提供服務(wù),。
進程間數(shù)據(jù)共享系統(tǒng)中的進程與其他進程共享CPU和主存資源,為了更好的管理主存,,現(xiàn)在系統(tǒng)提供了一種對主存的抽象概念,,即為虛擬存儲器(VM)。它是一個抽象的概念,,它為每一個進程提供了一個假象,,即每個進程都在獨占地使用主存。 虛擬存儲器主要提供了三個能力:
import multiprocessing import threading import time
n = 0
def count(num): global n for i in range(100000): n += i print('Process {0}:n={1},id(n)={2}'.format(num, n, id(n)))
if __name__ == '__main__': start_time = time.time() process = list() for i in range(5): p = multiprocessing.Process(target=count, args=(i,)) # 測試多進程使用 # p = threading.Thread(target=count, args=(i,)) # 測試多線程使用 process.append(p)
for p in process: p.start()
for p in process: p.join()
print('Main:n={0},id(n)={1}'.format(n, id(n))) end_time = time.time() print('Total time:{0}'.format(end_time - start_time))
什么是線程線程-也是操作系統(tǒng)提供的抽象概念,,是程序執(zhí)行中一個單一的順序控制流程,是程序執(zhí)行流的最小單元,,是處理器調(diào)度和分派的基本單位,。一個進程可以有一個或多個線程,同一進程中的多個線程將共享該進程中的全部系統(tǒng)資源,,如虛擬地址空間,,文件描述符和信號處理等等,。但同一進程中的多個線程有各自的調(diào)用棧和線程本地存儲(如下圖所示)。 系統(tǒng)利用PCB來完成對進程的控制和管理,。同樣,,系統(tǒng)為線程分配一個線程控制塊TCB(Thread Control Block),將所有用于控制和管理線程的信息記錄在線程的控制塊中,TCB中通常包括:
和進程一樣,,線程同樣有五種狀態(tài):初始態(tài),、執(zhí)行狀態(tài)、等待(阻塞)狀態(tài),、就緒狀態(tài)和終止狀態(tài),線程之間的切換和進程一樣也需要上下文切換,,這里不再贅述。
進程 VS 線程
什么是協(xié)程協(xié)程(Coroutine,,又稱微線程)是一種比線程更加輕量級的存在,協(xié)程不是被操作系統(tǒng)內(nèi)核所管理,,而完全是由程序所控制,。協(xié)程與線程以及進程的關(guān)系見下圖所示,。
協(xié)程適用于IO阻塞且需要大量并發(fā)的場景,,當發(fā)生IO阻塞,由協(xié)程的調(diào)度器進行調(diào)度,,通過將數(shù)據(jù)流yield掉,,并且記錄當前棧上的數(shù)據(jù),阻塞完后立刻再通過線程恢復棧,,并把阻塞的結(jié)果放到這個線程上去運行,。
如何選擇,?在針對不同的場景對比三者的區(qū)別之前,,首先需要介紹一下python的多線程(一直被程序員所詬病,認為是'假的'多線程),。
更換上面multiprocessing示例中,
Process 0:n=5756690257,id(n)=140103573185600 Process 2:n=10819616173,id(n)=140103573185600 Process 1:n=11829507727,id(n)=140103573185600 Process 4:n=17812587459,id(n)=140103573072912 Process 3:n=14424763612,id(n)=140103573185600 Main:n=17812587459,id(n)=140103573072912 Total time:0.1056210994720459
什么是GILGIL來源于Python設(shè)計之初的考慮,,為了數(shù)據(jù)安全(由于內(nèi)存管理機制中采用引用計數(shù))所做的決定。某個線程想要執(zhí)行,,必須先拿到 GIL,。因此,可以把 GIL 看作是“通行證”,并且在一個 Python進程中,,GIL 只有一個,拿不到通行證的線程,就不允許進入 CPU 執(zhí)行,。 Cpython解釋器在內(nèi)存管理中采用引用計數(shù),當對象的引用次數(shù)為0時,,會將對象當作垃圾進行回收,。設(shè)想這樣一種場景:
無論是單核還是多核,一個進程永遠只能同時執(zhí)行一個線程(拿到 GIL 的線程才能執(zhí)行,如下圖所示),,這就是為什么在多核CPU上,,Python 的多線程效率并不高的根本原因。
何時用,?常見的應用場景不外乎三種:
下面主要解釋一下I/O密集型的情況,。與I/O設(shè)備交互,,目前最常用的解決方案就是DMA,。 什么是DMADMA(Direct Memory Access)是系統(tǒng)中的一個特殊設(shè)備,它可以協(xié)調(diào)完成內(nèi)存到設(shè)備間的數(shù)據(jù)傳輸,,中間過程不需要CPU介入,。 以文件寫入為例:
Python多線程的表現(xiàn)(I/O密集型)
實踐是檢驗真理的唯一標準,,下面將針對I/O密集型場景進行測試,。 測試
## 多進程 Process 0 End Process 3 End Process 4 End Process 2 End Process 1 End Total time:1.383193016052246 ## 多線程 Process 0 End Process 4 End Process 3 End Process 1 End Process 2 End Total time:1.003425121307373
以Python中asyncio應用為依賴,使用async/await語法進行協(xié)程的創(chuàng)建和使用,。
total time: 1.001854419708252
總結(jié)本文從操作系統(tǒng)原理出發(fā)結(jié)合代碼實踐講解了進程,,線程和協(xié)程以及他們之間的關(guān)系。并且,,總結(jié)和整理了Python實踐中針對不同的場景如何選擇對應的方案,,如下:
|
|