需要先對 IO 的概念有一定的認識: IO在計算機中指Input/Output,也就是輸入和輸出。

並發與並行

並發:在操作系統中,某一時間段,幾個程序在同一個CPU上運行,但在任意一個時間點上,只有一個程序在CPU上運行。

當有多個線程時,如果系統只有一個CPU,那麼CPU不可能真正同時進行多個線程,CPU的運行時間會被劃分成若干個時間段,每個時間段分配給各個線程去執行,一個時間段里某個線程運行時,其他線程處於掛起狀態,這就是並發。並發解決了程序排隊等待的問題,如果一個程序發生阻塞,其他程序仍然可以正常執行。

並行:當操作系統有多個CPU時,一個CPU處理A線程,另一個CPU處理B線程,兩個線程互相不搶佔CPU資源,可以同時進行,這種方式成為並行。

區別
  1. 並發只是在宏觀上給人感覺有多個程序在同時運行,但在實際的單CPU系統中,每一時刻只有一個程序在運行,微觀上這些程序是分時交替執行。
  2. 在多CPU系統中,將這些並發執行的程序分配到不同的CPU上處理,每個CPU用來處理一個程序,這樣多個程序便可以實現同時執行。

知乎上高贊例子:

  • 你吃飯吃到一半,電話來了,你一直到吃完了以後才去接,這就說明你不支持並發也不支持並行。
  • 你吃飯吃到一半,電話來了,你停了下來接了電話,接完後繼續吃飯,這說明你支持並發。
  • 你吃飯吃到一半,電話來了,你一邊打電話一邊吃飯,這說明你支持並行。

並發的關鍵是你有處理多個任務的能力,不一定要同時。並行的關鍵是你有同時處理多個任務的能力。所以我認為它們最關鍵的點就是:是否是『同時』

進程

一個進程好比是一個程序,它是 資源分配的最小單位 。同一時刻執行的進程數不會超過核心數。不過如果問單核CPU能否運行多進程?答案又是肯定的。單核CPU也可以運行多進程,只不過不是同時的,而是極快地在進程間來回切換實現的多進程。舉個簡單的例子,就算是十年前的單核CPU的電腦,也可以聊QQ的同時看視頻。

電腦中有許多進程需要處於「同時」開啟的狀態,而利用CPU在進程間的快速切換,可以實現「同時」運行多個程序。而進程切換則意味著需要保留進程切換前的狀態,以備切換回去的時候能夠繼續接著工作。所以進程擁有自己的地址空間,全局變數,文件描述符,各種硬體等等資源。操作系統通過調度CPU去執行進程的記錄、回復、切換等等。

線程

如果說進程和進程之間相當於程序與程序之間的關係,那麼線程與線程之間就相當於程序內的任務和任務之間的關係。所以線程是依賴於進程的,也稱為 「微進程」 。它是 程序執行過程中的最小單元 。

一個程序內包含了多種任務。打個比方,用播放器看視頻的時候,視頻輸出的畫面和聲音可以認為是兩種任務。當你拖動進度條的時候又觸發了另外一種任務。拖動進度條會導致畫面和聲音都發生變化,如果進程里沒有線程的話,那麼可能發生的情況就是:

拖動進度條->畫面更新->聲音更新。你會明顯感到畫面和聲音和進度條不同步。

但是加上了線程之後,線程能夠共享進程的大部分資源,並參與CPU的調度。意味著它能夠在進程間進行切換,實現「並發」,從而反饋到使用上就是拖動進度條的同時,畫面和聲音都同步了。所以我們經常能聽到的一個詞是「多線程」,就是把一個程序分成多個任務去跑,讓任務更快處理。不過線程和線程之間由於某些資源是獨佔的,會導致鎖的問題。例如Python的GIL多線程鎖。

進程與線程的區別

  1. 進程是CPU資源分配的基本單位,線程是獨立運行和獨立調度的基本單位(CPU上真正運行的是線程)。
  2. 進程擁有自己的資源空間,一個進程包含若干個線程,線程與CPU資源分配無關,多個線程共享同一進程內的資源。
  3. 線程的調度與切換比進程快很多。

CPU密集型代碼(各種循環處理、計算等等):使用多進程。IO密集型代碼(文件處理、網路爬蟲等):使用多線程

阻塞與非阻塞

阻塞是指調用線程或者進程被操作系統掛起。非阻塞是指調用線程或者進程不會被操作系統掛起。

同步與非同步

同步是阻塞模式,非同步是非阻塞模式。

  • 同步就是指一個進程在執行某個請求的時候,若該請求需要一段時間才能返回信息,那麼這個進程將會一直等待下去,知道收到返回信息才繼續執行下去;
  • 非同步是指進程不需要一直等下去,而是繼續執行下面的操作,不管其他進程的狀態。當有消息返回式系統會通知進程進行處理,這樣可以提高執行的效率。

由調用方盲目主動問詢的方式是同步調用,由被調用方主動通知調用方任務已完成的方式是非同步調用。看下圖

協程

協程,又稱微線程,纖程。英文名Coroutine。一句話說明什麼是線程:協程是一種用戶態的輕量級線程。

協程擁有自己的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復先前保存的寄存器上下文和棧。因此:協程能保留上一次調用時的狀態(即所有局部狀態的一個特定組合),每次過程重入時,就相當於進入上一次調用的狀態,換種說法:進入上一次離開時所處邏輯流的位置。

協程的好處:
  1. 無需線程上下文切換的開銷
  2. 無需原子操作鎖定及同步的開銷
  3. 方便切換控制流,簡化編程模型

高並發+高擴展性+低成本:一個CPU支持上萬的協程都不是問題。所以很適合用於高並發處理。

缺點:
  1. 無法利用多核資源:協程的本質是個單線程,它不能同時將 單個CPU 的多個核用上,協程需要和進程配合才能運行在多CPU上.當然我們日常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。
  2. 進行阻塞(Blocking)操作(如IO時)會阻塞掉整個程序

最佳實踐

  1. 線程和協程推薦在IO密集型的任務(比如網路調用)中使用,而在CPU密集型的任務中,表現較差。
  2. 對於CPU密集型的任務,則需要多個進程,繞開GIL的限制,利用所有可用的CPU核心,提高效率。
  3. 所以大並發下的最佳實踐就是多進程+協程,既充分利用多核,又充分發揮協程的高效率,可獲得極高的性能。

順便一提,非常流行的一個爬蟲框架Scrapy就是用到非同步框架Twisted來進行任務的調度,這也是Scrapy框架高性能的原因之一。

最後推薦閱讀:深入理解 Python 非同步編程(上)

閱讀原文
推薦閱讀:
相关文章