最近在性能調試時,發現了一個有趣的現象,我把代碼簡化後如下.
可以看到,非常簡單的一段代碼,當我用Windows的性能監測工具來監測每個處理器的使用率時,發現了一個有趣的現象.
我電腦是四核的I7處理器,執行以上代碼後,卻隻有處理器2一直處理一個比較高的占用率,而其他的三個則處于一個"摸魚混日子"的狀态,處理器1則更過分,你是睡着了嗎?
同一台電腦上的處理器,難道大家不是有福同享,有難同當的嗎? 為什麼其他幾個處理器就忍心看着處理器2水深火熱呢?
然後,我就和這個問題死磕上了,惡補了一些作業系統與多線程的知識,現在把一寫知識點串起來,分享給大家.
電腦作業系統提供不同的資源通路級别。在計算機體系結構中,Rings是由兩個或更多的特權态組成。在一些硬體或者微代碼級别上提供不同特權态模式的CPU架構上,保護環通常都是硬體強制的。Rings是從最高特權級(通常被叫作0級)到最低特權級(通常對應最大的數字)排列的。在大多數作業系統中,Ring 0擁有最高特權,并且可以和最多的硬體直接互動(比如CPU,記憶體)。在Windows中, User Space,也就是我們自己安裝的那些應用程式處理Ring 3,而系統核心就在Ring 0.
對于這個問題,舉個例子,大家就好了解了.
錢不是萬能的,但沒錢是萬萬不能的,是以錢是一個家庭的重中之重,家裡老婆呢為了這個家的長治久安,掌握家裡的财政大權,把家裡的小金庫守得死死的,但這就意味着我沒錢花了嗎?當然不是,和老婆大人用正當理由申請不就完事了?😂
申請通過之後,老婆大人是允許我直接伸手去家裡小金庫拿錢嗎? 那當然不是,如果我一抓一大把就危險了,是以還得經過她的手從小金庫拿錢給我.
這個現象,我覺得也是一種分級保護域,是以呢,也一直對老婆大人的這種萬惡行徑表示了解.
作業系統也是這樣,CPU,記憶體這些硬體是電腦安全的根本,是以不能給第三方軟體操作權限,想操作硬體,就通過由Ring 0中核心(Kernel)暴露的嚴格Api進行.
線程主要有以下兩種實作方式-
使用者級線程 -使用者托管線程。
核心級線程 -作用在核心(作業系統核心)上的作業系統管理的線程。
在上圖中,User Space就可以了解為我上個章節中的Ring 3,而Kernel Space就是Ring 0, 在Ring 0中,是可以直接操作CPU,記憶體等硬體的,而Ring 3不行.
以下是使用者級線程與核心級線程的對比.
使用者級線程
核心級線程
使用者線程由使用者實作。
核心線程由OS實作。
作業系統無法識别使用者級線程。
核心線程被作業系統識别。
使用者線程的實作很容易。
核心線程的實作很複雜。
上下文切換時間更少。
上下文切換時間更長。
上下文切換不需要硬體支援。
需要硬體支援。
如果一個使用者級别的線程執行阻止操作,則整個過程将被阻止。
如果一個核心線程執行阻止操作,則另一線程可以繼續執行。
無法直接發揮多核處理器的優勢
可以享受多處理起帶來的好處
其中,非常重要的一點,使用者級線程無法直接發揮多核處理器的優勢,難道我們編寫出來的代碼隻能在一個處理器上運作了嗎?這就要講講使用者級線程模型.
通常,核心級線程可以使用三個模型之一來執行使用者級線程。
Many-to-one
One-to-one
Many-to-many
所有模型都将使用者級線程映射到核心級線程,一個核心線程就像一個處理器,它是系統編排任務的基本機關。
多對一模型将許多使用者級線程映射到一個核心級線程。線程管理是通過線程庫在使用者空間中完成的。當線程進行阻塞的系統調用時,整個過程将被阻塞。一次隻能有一個線程通路核心,是以多個線程無法在多處理器上并行運作。
如果使用者級線程庫是以作業系統不支援的方式實作的,則核心線程将使用多對一關系模型。
核心對使用者級線程不可見,在它眼裡隻有核心線程,而在核心線程的眼裡,一個程序無非就是一個偶爾被被它翻牌的黑盒子,程序負責使用者線程的排程與執行.
在這種模型下使用者級線程與核心級線程之間存在一對一的關系。該模型比多對一模型并發性好,當一個線程進行阻塞系統調用時,它還允許另一個線程運作,是以它支援多個線程以在處理器上并行執行。
該模型的缺點是建立使用者線程需要相應的核心線程,而建立核心線程開銷是很大的.
在多對多模型中,m個核心線程處理n個使用者線程,其中m < n. 該模型并發性最好,并且不用建立過多的核心線程,涉及到的線程切換同步的開銷也更小.
.Net的代碼作為托管代碼在“托管線程”上執行,而托管線程是在CLR虛拟機上執行的虛拟線程,也是屬于使用者級線程.
正如JIT編譯器将“虛拟” IL指令映射到在實體計算機上執行的本機指令一樣,CLR的線程基礎結構也将“虛拟”托管線程映射到作業系統提供的核心線程。
說到這裡,我們也差不多有了前面我說的那個現象的答案了,并非其他處理器不想與那個水深火熱的處理器有難同享,而是我沒有使用多線程,是以執行的程式隻有一個主線程,也就是說使用者線程數為1.隻能是one to one 模型,是以隻有一個處理器能參與工作.
既然知道了裡面的原理,那我們就對前文中的程式進行改造,建立四個線程來執行任務,會不會所有處理器都忙起來呢?
可以看到,這次大家的步伐都做到了驚人的一緻,四個處理器都被調用起來,加上主線程,這裡至少有五個使用者線程,是以這裡應該是many to many的模型了.
謝謝觀賞!
參考資料:
https://en.wikipedia.org/wiki/Protection_ring
https://stackoverflow.com/questions/15093510/whats-c-sharp-threading-type
https://github.com/dotnet/coreclr/blob/master/Documentation/botr/threading.md#clr-threading-overview