鳥,水螅,計算機。CPU給咱們的啓示

原創:小姐姐味道(微信公衆號ID:xjjdog),歡迎分享,轉載請保留出處。java

做爲一隻鳥,能夠邊吃東西邊拉屎麼?這對一個養過鸚鵡的人來講,答案是確定的。程序員

從鳥嘴到鳥大腸,整個過程是串行的。雖然有些曲折,但方向是單一的,出口是必定的。只要鳥可以克服一些心理上的不悅,它就能辦獲得。bash

這種狀況在一些原始的物種身上顯得有些尷尬。好比水螅,它屬於腔腸動物,無性生殖。這幾個單詞都挺唬人,但它的體內只有一個空腔。從哪裏吃,就從哪裏出。微信

同是地球上的物種,從長遠看來,咱們仍是它的近親。多線程

做爲高級哺乳動物,咱們可以在呼吸的時候,同時說話,也可以同時聽到聲音,看到賞心悅目的風景。若是你想的話,也能夠辦到鸚鵡作的事。架構

這些信息被收集以後,一股腦發送給大腦進行處理。有多是像深度學習同樣,存了一堆權重,但這些信息究竟是如何處理的,咱們如今還不得而知。併發

因此程序員們轉而研究計算機,畢竟這個相比起「最後歸途是哲學」的人類大腦,就像是個玩具。高併發


咱們都知道,幹一堆事幹很差,不如集中精力把一件事幹好。這並非說一我的沒能力把全部的事情幹好,而是在不一樣的事務之間切換,是要耗費資源的。學習

你的大腦好不容易熟悉了一個工做場景,結果忽然調度給它另一項任務,它就要花很長時間切換到新的工做場景中。this

有時候代碼寫多了,我就連說話都開始口吃。但一直不停的說說說,就又恢復了。

因此,全部的人都恨零零散散的工做事務。尤爲是恨哪些不斷給你小事情,但又絕不相關的任務的領導。

到頭來,感受作了不少,但一點成果都沒有,感受人都廢了。

不要怕,咱們看看CPU是怎麼處理的。


CPU處理任務時不是一直只處理一個,而是經過給每一個線程分配CPU時間片,時間片用完了就切換下一個線程。

時間片很是短,通常只有幾十毫秒,CPU經過不停地切換線程執行,但咱們幾乎感受不到任務的停滯。由於對人類來講,高質量的遊戲,每秒只須要60幀,就算是流暢了。

這個時間仍是至關可觀的,特別是在進程上下文切換次數較多的狀況下,很容易致使CPU將大量時間消耗在寄存器,內核棧以及虛擬內存等資源的保存和恢復上,進而大大縮短了真正運行進程的時間。

對於Linux來講。程序在執行過程當中一般有用戶態內核態兩種狀態,CPU對處於內核態根據上下文環境進一步細分,所以有了下面三種狀態:

  • 內核態,運行於進程上下文,內核表明進程運行於內核空間。
  • 內核態,運行於中斷上下文,內核表明硬件運行於內核空間。
  • 用戶態,運行於用戶空間

咱們看一下Linux的top命令,是怎麼顯示內核態和用戶態的。

如上圖,us就是user的意思;sy就是system的意思。分別表明了用戶態和內核態。

若是sy佔用的過高,就有多是上下文切換和中斷太頻繁了。

那什麼是上下文?

所謂的上下文,說白了就是一個環境。好比你去食堂帶着飯盒,去廁所帶着廁紙。要是搞亂了,去廁所帶着飯盒,感受上就不正常。操做系統爲每個進程,分配了這麼一個上下文,用來存放:代碼、數據、用戶堆棧、共享存儲區、寄存器、進程控制塊等。

先不要管裏面的細節了,反正內容不少,切換確定是要有陳本的。好比,廁紙放在家裏臥室櫃子的第三層小隔間。

vmstat命令顯示的這幾列,就是這麼個意思。cs不是csgo的縮寫,它的全拼是context switch

在每一個進程裏,也能夠看到累加的值。

[root@localhost ~]# cat /proc/2788/status
...
voluntary_ctxt_switches: 93950
nonvoluntary_ctxt_switches: 171204
複製代碼

cs若是過高,那就是線程或者進程開的太多了。


上下文切換又分爲2種。

讓步式上下文切換和搶佔式上下文切換。

下面先說下讓步式上下文切換。咱們拿Java中的cas操做來看就能夠了。

cas除了 compare and switch原始指令支持之外,還須要一個循環來保證。

public final long getAndAddLong(Object var1, long var2, long var4) {
        long var6;
        do {
            var6 = this.getLongVolatile(var1, var2);
        } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
        return var6;
}
複製代碼

代碼放在循環裏,在併發量比較高的狀況下,若是許多線程反覆嘗試更新某一個變量,卻又一直更新不成功,循環往復,會給CPU帶來很大的壓力。因此,讓步式上下文切換,是指執行線程主動釋放CPU,與鎖競爭嚴重程度成正比,可經過減小鎖競爭來避免。

而搶佔式上下文切換,是指線程因分配的時間片用盡而被迫放棄CPU,或者被其餘優先級更高的線程所搶佔。通常因爲線程數大於CPU可用核心數引發,可經過調整線程數,適當減小線程數來避免。

那爲啥Java的線程就可以比多進程速度快一些呢?由於Java的線程本質上也是一種輕量級進程,但它的虛擬內存等信息是共享的,只須要切換線程的私有數據,寄存器等不共享的數據。即便這樣,也會耗費很多時間。

使用perf命令一樣可以觀測到這個上下文切換到過程和數量。好比:

# 跟蹤全部上下文切換,直到Ctrl-C:
perf record -e context-switches -c 1 -a

# 包括使用的原始設置(請參閱:man perf_event_open):
perf record -vv -e context-switches -a

# 使用堆棧跟蹤的示例上下文切換,直到Ctrl-C:
perf record -e context-switches -ag
複製代碼

使用perf report便可查看相關結果。

對於計算機來講,效率最高的依然是專心作一件事。必定程度上,你也算是計算機的老闆。若是你一直讓它幹一些雜活,把它當牛使,那你的計算機效率不必定會高。

有些人很聰明,他必定知道這種來回切換的方式對你的工做效率影響巨大。排除他愚蠢的屬性,就只剩下壞:給你一堆爛七八糟的事,搞得你身心疲憊,最後又和你講結果導向的---必定是你的領導故意爲之。

做者簡介:小姐姐味道 (xjjdog),一個不容許程序員走彎路的公衆號。聚焦基礎架構和Linux。十年架構,日百億流量,與你探討高併發世界,給你不同的味道。個人我的微信xjjdog0,歡迎添加好友,​進一步交流。​

相關文章
相關標籤/搜索