程序運行的關鍵是什么呢?當然是CPU,沒有CPU程序就無法執行。CPU的角色就像打工人,比如你想買一套煎餅馃子,早餐攤上得有人給你攤才行,那些原料不會自己組裝成一套煎餅馃子。
按照我們通常的認識,如果我們想擴大業務,常規的思路就是多招人。比如煎餅馃子攤再招個人來做雞蛋灌餅,這樣就可以同時讓一個人攤煎餅馃子,一個人做雞蛋灌餅。按程序的運行來說就是多加CPU,這種運行模式被稱為并行。
但是多加CPU很合適嗎?給電腦安裝五六個CPU,感覺像在給主板貼瓷磚,實在是太占地方了。不過隨著技術的發展我們可以把多個CPU的核心封裝到一個CPU里,這就是CPU核心數的意義。經常能看到哪個CPU宣傳自己是“八核十六線程”,這里的“八核”就是指一個CPU里有八個核心,四舍五入等于八個CPU。
那“十六線程”是什么意思呢?不急,我們先來看看現在這種狀況有什么不合適的。如果我們的煎餅馃子攤有一個人專做煎餅馃子,一個人專做雞蛋灌餅,會有什么后果?假如這一波顧客都只要煎餅馃子,那么做大餅雞蛋的員工就是在帶薪摸魚,相當于搶劫我們資本家了。一個打工人上班只有一件事,那這個公司不是在創業,是在搞慈善。所以關鍵問題就是員工的利用率太低了。
實際上我們只讓一個員工來做這兩件事就好了。而在程序運行中,每件事被稱為進程。攤煎餅馃子,做雞蛋灌餅都是進程。我們運行一個QQ,再運行一個微信,這兩者也都是進程。
然而俗話說得好,一心不能二用。一個員工如何能同時即攤煎餅馃子又做雞蛋灌餅呢?最簡單的辦法就是一個一個地做,攤完煎餅馃子再做雞蛋灌餅,這種模式被稱為串行。但缺點就是不能同時出餐,有些客人可能要等很長時間。那該怎么優化流程呢?
其實我們可以把每件事拆分成很多子任務,比如攤煎餅馃子就可以分為加糊,攤勻,加蛋,撒蔥花,翻面,刷醬,加馃子,折疊,裝袋等任務。我們盡可能地細分任務,分到不能再分。這樣執行這種最小任務的單位就被稱為線程。我們可以讓員工執行一下攤煎餅馃子的任務,再執行一下做雞蛋灌餅的任務,在這兩個進程中反復橫跳,只要速度夠快,人分辨不出來,就可以實現兩件事“同時”進行的效果。(有點像七龍珠里的殘象拳)
特別是有些任務是需要等時間的,比如在攤煎餅馃子時把面糊攤勻后需要等個十幾秒,這個時候讓員工去打雞蛋為做雞蛋灌餅做準備,時間的利用率就又提高了,出餐的速度也就更快了。那么這樣以“反復橫跳”的方式用一個人同時完成多個任務的模式就被稱作并發。
不過并發并非適合所有情況,畢竟“反復橫跳”也是要花時間的,如果需要切換的頻率太高,可能效率反而不如串行。
那么再回到我們剛才的“八核十六線程”。一個CPU的核心只能同時進行一個線程,在以前CPU的核心數等于線程數。但2002年英特爾研發出了超線程技術,將單個核心模擬成多個核心,就像鳴人開了影分身。
但模擬的畢竟是模擬的,不但運行起來有軟硬件的限制,單個線程的效率還比物理核心低下,因此所謂的“八核十六線程”不能等同于十六核。
雖然我們剛才使用早點攤來解釋這些概念,但在一臺電腦中實際的運行狀況要遠比早點攤復雜,想想你打開電腦,登上微信,打開QQ,啟動迅雷,再放個音樂,加上很多后臺運行的程序,進程數輕松破百。這更像是在一個大企業中(電腦主機)有非常多的項目組(進程),企業的員工(CPU核心/線程)在各個項目組中來回奔波,某些項目在同時進行(并行),某些項目在來回切換著做(并發),某些項目在按著順序一步一步來(串行),而我們作為資本家需要的是合理地設置業務結構,讓所有員工時時刻刻都有活干。
在我們之前的編程學習中,我們都是按照串行的方式來進行的。在一定條件下,串行是效率低下的代表,我們以后也會接觸到并行和并發編程。比如在爬蟲中,我們可能一次循環爬取多個網頁,但獲取網頁涉及到網絡連接,磁盤讀寫,需要花費的時間就比較長了,我們就可以在上一個網頁還在獲取的過程中直接切換到下一個網頁并進行獲取,這樣就可以大幅加快爬蟲的速度。
最后我們來總結一下:
進程:每一個程序,每一件事都是一個進程,像是一個企業中的項目組。
線程:執行進程中最小任務的單位,相當于項目組中的崗位。一個CPU核心或線程只能同時執行一個線程。
串行:按照順序一步一步執行進程,做完一個再做另一個,二者不同時進行。
并行:用多個線程同時執行多個進程,多個進程完全同時進行。
并發:用單個線程以反復橫跳來回切換的方式假裝多個進程在同時進行。