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是單核還是多核。
但當你需要利用線程充分發揮多核威力時,通常情況下你創建的線程數與核數要保持一種線性關係,最佳係數通常需要測試才能得到。
希望這篇文章對大家理解多核以及多線程有所幫助。

本文分享自微信公衆號 - 音視頻開發進階(glumes_blog)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章