當(dāng)前位置:首頁 > 公眾號精選 > 程序員小灰
[導(dǎo)讀]本文從操作系統(tǒng)原理出發(fā)結(jié)合代碼實(shí)踐講解了以下內(nèi)容: 什么是進(jìn)程,線程和協(xié)程? 它們之間的關(guān)系是什么? 為什么說Python中的多線程是偽多線程? 不同的應(yīng)用場景該如何選擇技術(shù)方案? ... 什么是進(jìn)程 進(jìn)程-操作系統(tǒng)提供的抽象概念,是系統(tǒng)進(jìn)行資源分配和調(diào)度

本文從操作系統(tǒng)原理出發(fā)結(jié)合代碼實(shí)踐講解了以下內(nèi)容:

  • 什么是進(jìn)程,線程和協(xié)程?

  • 它們之間的關(guān)系是什么?

  • 為什么說Python中的多線程是偽多線程?

  • 不同的應(yīng)用場景該如何選擇技術(shù)方案?

  • ...

什么是進(jìn)程

進(jìn)程-操作系統(tǒng)提供的抽象概念,是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位,是操作系統(tǒng)結(jié)構(gòu)的基礎(chǔ)。程序是指令、數(shù)據(jù)及其組織形式的描述,進(jìn)程是程序的實(shí)體。程序本身是沒有生命周期的,它只是存在磁盤上的一些指令,程序一旦運(yùn)行就是進(jìn)程。

當(dāng)程序需要運(yùn)行時,操作系統(tǒng)將代碼和所有靜態(tài)數(shù)據(jù)記載到內(nèi)存和進(jìn)程的地址空間(每個進(jìn)程都擁有唯一的地址空間)中,通過創(chuàng)建和初始化棧(局部變量,函數(shù)參數(shù)和返回地址)、分配堆內(nèi)存以及與IO相關(guān)的任務(wù),當(dāng)前期準(zhǔn)備工作完成,啟動程序,OS將CPU的控制權(quán)轉(zhuǎn)移到新創(chuàng)建的進(jìn)程,進(jìn)程開始運(yùn)行。

操作系統(tǒng)對進(jìn)程的控制和管理通過PCB(Processing Control Block),PCB通常是系統(tǒng)內(nèi)存占用區(qū)中的一個連續(xù)存區(qū),它存放著操作系統(tǒng)用于描述進(jìn)程情況及控制進(jìn)程運(yùn)行所需的全部信息(進(jìn)程標(biāo)識號,進(jìn)程狀態(tài),進(jìn)程優(yōu)先級,文件系統(tǒng)指針以及各個寄存器的內(nèi)容等),進(jìn)程的PCB是系統(tǒng)感知進(jìn)程的唯一實(shí)體。

一個進(jìn)程至少具有5種基本狀態(tài):初始態(tài)、執(zhí)行狀態(tài)、等待(阻塞)狀態(tài)、就緒狀態(tài)、終止?fàn)顟B(tài)

  • 初始狀態(tài):進(jìn)程剛被創(chuàng)建,由于其他進(jìn)程正占有CPU所以得不到執(zhí)行,只能處于初始狀態(tài)。

  • 執(zhí)行狀態(tài):任意時刻處于執(zhí)行狀態(tài)的進(jìn)程只能有一個。

  • 就緒狀態(tài):只有處于就緒狀態(tài)的經(jīng)過調(diào)度才能到執(zhí)行狀態(tài)

  • 等待狀態(tài):進(jìn)程等待某件事件完成

  • 停止?fàn)顟B(tài):進(jìn)程結(jié)束

進(jìn)程間的切換

無論是在多核還是單核系統(tǒng)中,一個CPU看上去都像是在并發(fā)的執(zhí)行多個進(jìn)程,這是通過處理器在進(jìn)程間切換來實(shí)現(xiàn)的。

操作系統(tǒng)對把CPU控制權(quán)在不同進(jìn)程之間交換執(zhí)行的機(jī)制成為上下文切換(context switch),即保存當(dāng)前進(jìn)程的上下文,恢復(fù)新進(jìn)程的上下文,然后將CPU控制權(quán)轉(zhuǎn)移到新進(jìn)程,新進(jìn)程就會從上次停止的地方開始。因此,進(jìn)程是輪流使用CPU的,CPU被若干進(jìn)程共享,使用某種調(diào)度算法來決定何時停止一個進(jìn)程,并轉(zhuǎn)而為另一個進(jìn)程提供服務(wù)。

  • 單核CPU雙進(jìn)程的情況 

進(jìn)程直接特定的機(jī)制和遇到I/O中斷的情況下,進(jìn)行上下文切換,輪流使用CPU資源

  • 雙核CPU雙進(jìn)程的情況 

每一個進(jìn)程獨(dú)占一個CPU核心資源,在處理I/O請求的時候,CPU處于阻塞狀態(tài)

進(jìn)程間數(shù)據(jù)共享

系統(tǒng)中的進(jìn)程與其他進(jìn)程共享CPU和主存資源,為了更好的管理主存,現(xiàn)在系統(tǒng)提供了一種對主存的抽象概念,即為虛擬存儲器(VM)。它是一個抽象的概念,它為每一個進(jìn)程提供了一個假象,即每個進(jìn)程都在獨(dú)占地使用主存。

虛擬存儲器主要提供了三個能力:

  • 將主存看成是一個存儲在磁盤上的高速緩存,在主存中只保存活動區(qū)域,并根據(jù)需要在磁盤和主存之間來回傳送數(shù)據(jù),通過這種方式,更高效地使用主存

  • 為每個進(jìn)程提供了一致的地址空間,從而簡化了存儲器管理

  • 保護(hù)了每個進(jìn)程的地址空間不被其他進(jìn)程破壞

由于進(jìn)程擁有自己獨(dú)占的虛擬地址空間,CPU通過地址翻譯將虛擬地址轉(zhuǎn)換成真實(shí)的物理地址,每個進(jìn)程只能訪問自己的地址空間。因此,在沒有其他機(jī)制(進(jìn)程間通信)的輔助下,進(jìn)程之間是無法共享數(shù)據(jù)的

  • 以python中multiprocessing為例

import multiprocessingimport threadingimport 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,)) # 測試多進(jìn)程使用 # 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))
  • 結(jié)果

Process 1:n=4999950000,id(n)=139854202072440Process 0:n=4999950000,id(n)=139854329146064Process 2:n=4999950000,id(n)=139854202072400Process 4:n=4999950000,id(n)=139854201618960Process 3:n=4999950000,id(n)=139854202069320Main:n=0,id(n)=9462720Total time:0.03138256072998047

變量n在進(jìn)程p{0,1,2,3,4}和主進(jìn)程(main)中均擁有唯一的地址空間

什么是線程

線程-也是操作系統(tǒng)提供的抽象概念,是程序執(zhí)行中一個單一的順序控制流程,是程序執(zhí)行流的最小單元,是處理器調(diào)度和分派的基本單位。一個進(jìn)程可以有一個或多個線程,同一進(jìn)程中的多個線程將共享該進(jìn)程中的全部系統(tǒng)資源,如虛擬地址空間,文件描述符和信號處理等等。但同一進(jìn)程中的多個線程有各自的調(diào)用棧和線程本地存儲。

系統(tǒng)利用PCB來完成對進(jìn)程的控制和管理。同樣,系統(tǒng)為線程分配一個線程控制塊TCB(Thread Control Block),將所有用于控制和管理線程的信息記錄在線程的控制塊中,TCB中通常包括:

  • 線程標(biāo)志符

  • 一組寄存器

  • 線程運(yùn)行狀態(tài)

  • 優(yōu)先級

  • 線程專有存儲區(qū)

  • 信號屏蔽

和進(jìn)程一樣,線程同樣有五種狀態(tài):初始態(tài)、執(zhí)行狀態(tài)、等待(阻塞)狀態(tài)、就緒狀態(tài)和終止?fàn)顟B(tài),線程之間的切換和進(jìn)程一樣也需要上下文切換,這里不再贅述。

進(jìn)程和線程之間有許多相似的地方,那它們之間到底有什么區(qū)別呢?

進(jìn)程 VS 線程

  • 進(jìn)程是資源的分配和調(diào)度的獨(dú)立單元。進(jìn)程擁有完整的虛擬地址空間,當(dāng)發(fā)生進(jìn)程切換時,不同的進(jìn)程擁有不同的虛擬地址空間。而同一進(jìn)程的多個線程是可以共享同一地址空間

  • 線程是CPU調(diào)度的基本單元,一個進(jìn)程包含若干線程。

  • 線程比進(jìn)程小,基本上不擁有系統(tǒng)資源。線程的創(chuàng)建和銷毀所需要的時間比進(jìn)程小很多

  • 由于線程之間能夠共享地址空間,因此,需要考慮同步和互斥操作

  • 一個線程的意外終止會影像整個進(jìn)程的正常運(yùn)行,但是一個進(jìn)程的意外終止不會影像其他的進(jìn)程的運(yùn)行。因此,多進(jìn)程程序安全性更高。

總之,多進(jìn)程程序安全性高,進(jìn)程切換開銷大,效率低;多線程程序維護(hù)成本高,線程切換開銷小,效率高。(python的多線程是偽多線程,下文中將詳細(xì)介紹

什么是協(xié)程

協(xié)程(Coroutine,又稱微線程)是一種比線程更加輕量級的存在,協(xié)程不是被操作系統(tǒng)內(nèi)核所管理,而完全是由程序所控制。

  • 協(xié)程可以比作子程序,但執(zhí)行過程中,子程序內(nèi)部可中斷,然后轉(zhuǎn)而執(zhí)行別的子程序,在適當(dāng)?shù)臅r候再返回來接著執(zhí)行。協(xié)程之間的切換不需要涉及任何系統(tǒng)調(diào)用或任何阻塞調(diào)用

  • 協(xié)程只在一個線程中執(zhí)行,是子程序之間的切換,發(fā)生在用戶態(tài)上。而且,線程的阻塞狀態(tài)是由操作系統(tǒng)內(nèi)核來完成,發(fā)生在內(nèi)核態(tài)上,因此協(xié)程相比線程節(jié)省線程創(chuàng)建和切換的開銷

  • 協(xié)程中不存在同時寫變量沖突,因此,也就不需要用來守衛(wèi)關(guān)鍵區(qū)塊的同步性原語,比如互斥鎖、信號量等,并且不需要來自操作系統(tǒng)的支持。

協(xié)程適用于IO阻塞且需要大量并發(fā)的場景,當(dāng)發(fā)生IO阻塞,由協(xié)程的調(diào)度器進(jìn)行調(diào)度,通過將數(shù)據(jù)流yield掉,并且記錄當(dāng)前棧上的數(shù)據(jù),阻塞完后立刻再通過線程恢復(fù)棧,并把阻塞的結(jié)果放到這個線程上去運(yùn)行。

下面,將針對在不同的應(yīng)用場景中如何選擇使用Python中的進(jìn)程,線程,協(xié)程進(jìn)行分析。

如何選擇?

在針對不同的場景對比三者的區(qū)別之前,首先需要介紹一下python的多線程(一直被程序員所詬病,認(rèn)為是"假的"多線程)。

那為什么認(rèn)為Python中的多線程是“偽”多線程呢?

更換上面multiprocessing示例中,p=multiprocessing.Process(target=count,args=(i,))p=threading.Thread(target=count,args=(i,)),其他照舊,運(yùn)行結(jié)果如下:

為了減少代碼冗余和文章篇幅,命名和打印不規(guī)則問題請忽略

Process 0:n=5756690257,id(n)=140103573185600Process 2:n=10819616173,id(n)=140103573185600Process 1:n=11829507727,id(n)=140103573185600Process 4:n=17812587459,id(n)=140103573072912Process 3:n=14424763612,id(n)=140103573185600Main:n=17812587459,id(n)=140103573072912Total time:0.1056210994720459
  • n是全局變量,Main的打印結(jié)果與線程相等,證明了線程之間是數(shù)據(jù)共享

但是,為什么多線程運(yùn)行時間比多進(jìn)程還要長?這與我們上面所說(線程的開銷<<進(jìn)程的開銷)的嚴(yán)重不相符啊。這就是輪到Cpython(python默認(rèn)的解釋器)中GIL(Global Interpreter Lock,全局解釋鎖)登場了。

什么是GIL

GIL來源于Python設(shè)計之初的考慮,為了數(shù)據(jù)安全(由于內(nèi)存管理機(jī)制中采用引用計數(shù))所做的決定。某個線程想要執(zhí)行,必須先拿到 GIL。因此,可以把 GIL 看作是“通行證”,并且在一個 Python進(jìn)程中,GIL 只有一個,拿不到通行證的線程,就不允許進(jìn)入 CPU 執(zhí)行。

Cpython解釋器在內(nèi)存管理中采用引用計數(shù),當(dāng)對象的引用次數(shù)為0時,會將對象當(dāng)作垃圾進(jìn)行回收。設(shè)想這樣一種場景:

一個進(jìn)程中含有兩個線程,分別為線程0和線程1,兩個線程全都引用對象a。當(dāng)兩個線程同時對a發(fā)生引用(并未修改,不需要使用同步性原語),就會發(fā)生同時修改對象a的引用計數(shù)器,造成計數(shù)器引用少于實(shí)質(zhì)性的引用,當(dāng)進(jìn)行垃圾回收時,造成錯誤異常。因此,需要一把全局鎖(即為GIL)來保證對象引用計數(shù)的正確性和安全性。

無論是單核還是多核,一個進(jìn)程永遠(yuǎn)只能同時執(zhí)行一個線程(拿到 GIL 的線程才能執(zhí)行),這就是為什么在多核CPU上,Python 的多線程效率并不高的根本原因。

那是不是在Python中遇到并發(fā)的需求就使用多進(jìn)程就萬事大吉了呢?其實(shí)不然,軟件工程中有一句名言:沒有銀彈!

何時用?

常見的應(yīng)用場景不外乎三種:

  • CPU密集型:程序需要占用CPU進(jìn)行大量的運(yùn)算和數(shù)據(jù)處理;

  • I/O密集型:程序中需要頻繁的進(jìn)行I/O操作;例如網(wǎng)絡(luò)中socket數(shù)據(jù)傳輸和讀取等;

  • CPU密集+I/O密集:以上兩種的結(jié)合

CPU密集型的情況可以對比以上multiprocessing和threading的例子,多進(jìn)程的性能 > 多線程的性能。

下面主要解釋一下I/O密集型的情況。與I/O設(shè)備交互,目前最常用的解決方案就是DMA。

什么是DMA

DMA(Direct Memory Access)是系統(tǒng)中的一個特殊設(shè)備,它可以協(xié)調(diào)完成內(nèi)存到設(shè)備間的數(shù)據(jù)傳輸,中間過程不需要CPU介入。

以文件寫入為例:

  • 進(jìn)程p1發(fā)出數(shù)據(jù)寫入磁盤文件的請求

  • CPU處理寫入請求,通過編程告訴DMA引擎數(shù)據(jù)在內(nèi)存的位置,要寫入數(shù)據(jù)的大小以及目標(biāo)設(shè)備等信息

  • CPU處理其他進(jìn)程p2的請求,DMA負(fù)責(zé)將內(nèi)存數(shù)據(jù)寫入到設(shè)備中

  • DMA完成數(shù)據(jù)傳輸,中斷CPU

  • CPU從p2上下文切換到p1,繼續(xù)執(zhí)行p1

Python多線程的表現(xiàn)(I/O密集型)

  • 線程Thread0首先執(zhí)行,線程Thread1等待(GIL的存在)

  • Thread0收到I/O請求,將請求轉(zhuǎn)發(fā)給DMA,DMA執(zhí)行請求

  • Thread1占用CPU資源,繼續(xù)執(zhí)行

  • CPU收到DMA的中斷請求,切換到Thread0繼續(xù)執(zhí)行

與進(jìn)程的執(zhí)行模式相似,彌補(bǔ)了GIL帶來的不足,又由于線程的開銷遠(yuǎn)遠(yuǎn)小于進(jìn)程的開銷,因此,在IO密集型場景中,多線程的性能更高

實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn),下面將針對I/O密集型場景進(jìn)行測試。

測試

  • 執(zhí)行代碼

import multiprocessingimport threadingimport time  def count(num): time.sleep(1) ## 模擬IO操作 print("Process {0} End".format(num))  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()  end_time = time.time() print("Total time:{0}".format(end_time - start_time))
  • 結(jié)果

## 多進(jìn)程Process 0 EndProcess 3 EndProcess 4 EndProcess 2 EndProcess 1 EndTotal time:1.383193016052246## 多線程Process 0 EndProcess 4 EndProcess 3 EndProcess 1 EndProcess 2 EndTotal time:1.003425121307373
  • 多線程的執(zhí)行效性能高于多進(jìn)程

是不是認(rèn)為這就結(jié)束了?遠(yuǎn)還沒有呢。針對I/O密集型的程序,協(xié)程的執(zhí)行效率更高,因?yàn)樗浅绦蜃陨硭刂频?,這樣將節(jié)省線程創(chuàng)建和切換所帶來的開銷。

以Python中asyncio應(yīng)用為依賴,使用async/await語法進(jìn)行協(xié)程的創(chuàng)建和使用。

  • 程序代碼

import timeimport asyncio  async def coroutine(): await asyncio.sleep(1) ## 模擬IO操作  if __name__ == "__main__": start_time = time.time()  loop = asyncio.get_event_loop() tasks = [] for i in range(5): task = loop.create_task(coroutine()) tasks.append(task)  loop.run_until_complete(asyncio.wait(tasks)) loop.close() end_time = time.time() print("total time:", end_time - start_time)
  • 結(jié)果

total time: 1.001854419708252
  • 協(xié)程的執(zhí)行效性能高于多線程

總結(jié)

本文從操作系統(tǒng)原理出發(fā)結(jié)合代碼實(shí)踐講解了進(jìn)程,線程和協(xié)程以及他們之間的關(guān)系。并且,總結(jié)和整理了Python實(shí)踐中針對不同的場景如何選擇對應(yīng)的方案,如下:

  • CPU密集型:多進(jìn)程

  • IO密集型:多線程(協(xié)程維護(hù)成本較高,而且在讀寫文件方面效率沒有顯著提升)

  • CPU密集和IO密集:多進(jìn)程+協(xié)程



—————END—————



喜歡本文的朋友,歡迎關(guān)注公眾號 程序員小灰,收看更多精彩內(nèi)容

       點(diǎn)個[在看],是對小灰最大的支持! 
		


免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點(diǎn),不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險,如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運(yùn)營商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學(xué)會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團(tuán))股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉