CPU 核數與線程數有什么關系?
作為一名美食資淺愛好者,盡管小風哥我廚藝拙計,但依然阻擋不了我對烹飪的熱愛。
那小風哥我通常是怎么做菜的呢?
大廚與菜譜
你沒猜錯,做菜之前先去下一份菜譜,照著菜譜一步步來:起鍋燒油、蔥姜蒜末下鍋爆香、倒入切好的食材、大火翻炒、加入適量醬油、加入適量鹽、繼續翻炒、出鍋嘍!
這樣一道色香味俱佳的小炒大功告成,裝盤端出來拿起筷子一嘗,難吃死了。
火候有點過,醬油加的有點少,鹽加多了,中餐里的“火候”以及“適量”是最為神秘的存在,可以意會不可言傳。因此相對肯德基麥當勞之類的標準工業品,中餐更像是藝術。每個人炒出來的菜味道都不一樣,顯然嘛,每個人對火候以及適量的理解是不一樣的。
對不起,跑題了。
雖然小風哥我廚藝不怎么樣,但輸廚藝不能輸氣場,有時我會幾樣一起來,這邊炒著A菜,那邊炒著B菜。
也就是說,我可以同時按照兩份菜譜去做飯,如果小風哥足夠快,那么我可以同時炒 N 樣菜。
炒菜與線程
實際上CPU和廚師一樣,都是按照菜譜(機器指令)去執行某個動作,從操作系統的角度講當CPU切換回用戶態后,CPU執行的一段指令就是線程,或者說屬于某個線程。
這和炒菜一樣,我可以按照菜譜抄魚香肉絲,那么炒菜時這就是魚香肉絲線程;我可以按照菜譜抄宮保雞丁,那么炒菜時這就是宮保雞丁線程。
廚師個數就好比CPU核心數,炒菜的樣數就好比線程數,這時我問你,你覺得廚師的個數和可以同時抄幾樣菜有關系嗎?
答案當然是沒有。
CPU的核心數和線程個數沒有什么必然的關系。
單個核心上可以跑任意多個線程,只要你的內存夠就行;計算機系統內也可以有任意多核數,只要你有錢就行。
看到這個答案你是不是覺得有點疑惑、有點疑問、有點不明所以,這好像和其它人說的不一樣啊!
別著急,我們慢慢講。
傻傻的CPU
CPU根本不理解自己執行的指令屬于哪個線程,CPU也不需要理解這些,CPU需要做的事情就是根據PC寄存器中的地址從內存中取出后執行,其它沒了。
你看CPU才不管你系統內有多少線程。
有多少線程是誰需要來關心的呢?是操作系統。
線程是操作系統的把戲。
操作系統與多任務
很久很久以前,計算機一次只能執行一個任務,你不能像現在這樣在計算機上一邊看電影一邊在下小電影,哦,不對,一邊寫代碼,一邊下載資料。
要么你先寫代碼,寫完代碼后再去下資料,要么你先下資料然后再寫代碼,總之,這兩個任務不能同時進行。
這顯然很不方便,就這樣,多任務——Multi-Tasking,誕生了。
你CPU不是只知道執行機器指令嗎?很好,那我操作系統就通過修改你的PC寄存器,讓你CPU執行A任務的機器指令一段時間,然后下一段時間再去執行B任務的機器指令,再然后下一個時間段去執行C任務的機器指令,由于每一段時間非常少,通常在毫秒級別,那么在人類看來A、B、C三個任務在“同時”運行。
這就是多任務的本質。
進程與線程
CPU不知道執行的某一段機器指令屬于A任務還是B任務,只有操作系統知道,同時操作系統還能知道任務A和B任務是否屬于同一個地址空間。
如果屬于同一個地址空間,那么任務A和任務B就是我們熟悉的“多線程”;如果不屬于同一個地址空間,那么任務A和任務B就是我們熟悉的“多進程”,現在你應該明白這兩個概念了吧。
這里出現了一個有點拗口的名詞,地址空間,Address Space,關于地址空間的概念以及進程線程這一部分更加詳細的講解,請參考小風哥的《深入理解操作系統》第7章,關注公眾號"碼農的荒島求生"并回復”操作系統“即可。
值得注意的是,計算機系統還在單核時代就已經有多線程的概念了,我們之前說過,即使是單核也可以執行多個線程,那么有的同學可能會有疑問,在單核的系統中開啟多個線程有什么意義嗎?
單核與多線程
假設現在有兩個任務,任務A和任務B,每個任務需要的計算時間都是5分鐘,那么無論是任務A和任務B串行執行還是放到兩個線程中并行執行,在單核環境下執行完這兩個任務總需要10分鐘,因此有的同學覺得單核下多線程沒什么用。
實際上,線程這個概念為程序員提供了一種編程抽象,我們可以把一項任務進行劃分,然后把每一個子任務放到一個個線程中去運行。
假如你的程序帶有圖形界面,某個UI元素背后需要的大量運算,這時為了防止執行該運算時UI產生卡頓,那么可以把這個運算任務放到一個單獨的線程中去。
因此如果你的目的是防止當前線程因執行某項操作而不得不等待,那么在這樣的應用場景下,你根本就不需要關心系統內是單核還是多核以及有多少個核。
阻塞式I/O
這也是使用線程的經典場景。
如果沒有線程,那么執行阻塞式I/O時整個進程會被操作系統暫停,但如果你開啟兩個線程,其中一個線程被阻塞時另一個線程依然可以繼續向前推進。
這樣的話你就不需要去使用反人類的異步IO了。
當然,這一切的前提是你的場景不涉及高性能以及高并發,如果涉及的話那這就是另一個話題了,如果你想了解這一話題,關注公眾號“碼農的荒島求生”并回復“高并發”即可。
在這種簡單的場景下,你創建線程時也不需要關心系統中是單核還是多核。
多核時代
實際上,線程這個概念是從2003年左右才開始流行的,為什么?因為這一時期,多核時代到來了。
之所以產生多核,是因為單核的性能提升越來越困難了。
盡管采用多進程也可以充分利用多核,但畢竟多進程編程是很繁瑣的,這涉及復雜的進程間通信機制、進程間切換的較高性能損耗、進程間內存相互隔離帶來的對內存消耗等。
線程這個概念很好的解決了上述問題,開始成為多核時代的主角,要想充分利用多核資源,線程是程序員的首選工具。
真正的并行
有了多核后,運行在兩個線程中的任務A和任務B實現了真正的并行。
此前這樣一句話廣為引用,這句話是這么說的:
threads are for people who can't program state machines
“線程是為那些不懂狀態機的人準備的”,這句話在單核時代有它的道理,因為在單核時代,所有的任務都不是在同時向前推進,而是“交錯”前進,A前進一點,然后B前進一點,線程并不是實現這種“偽并行”唯一的方法,狀態機也可以。
但在多核時代,這句話就不再適用了,對于大多數程序員來說多進程多線程幾乎是充分利用多核資源的唯一方法。
如果你的場景是想充分利用多核,那么這時你的確需要知道系統內有多少核數,一般來說你創建的線程數需要與核數保持線性關系。
也就是說,如果你的核數翻倍,那么創建的線程數也要翻倍。
需要多少線程?
值得注意的是,線程不是越多越好。
如果你的線程是不涉及任何I/O、沒有任何同步互斥之類的純計算類型,那么每個核心一個線程通常是最佳選擇。但通常來說,線程都需要一定的I/O,可能需要一定的同步互斥,那么這時適當增加線程可能會提高性能,但當線程數量到達一個臨界值后性能開始下降,這時線程間切換的開銷將顯著增加。
這里之所以用適當這個詞,是因為這很難去量化,只能用你實際的程序根據真正的場景進行測試才能得到這個值。
總結
線程數和CPU核心數可以沒有任何關聯,如果在使用線程時僅僅針對上述提到的幾個簡單場景,那么你根本不需要關心CPU是單核還是多核。
但當你需要利用線程充分發揮多核威力時,通常情況下你創建的線程數與核數要保持一種線性關系,最佳系數通常需要測試才能得到。
我是小風哥,希望這篇文章對大家理解多核以及多線程有所幫助。