從今天開始看《并發(fā)編程的藝術(shù)》,然后留下學(xué)習(xí)的筆記,是學(xué)習(xí),是提問,是散發(fā)思維,是形成自己的知識體系。抱著學(xué)習(xí)研究的態(tài)度,去學(xué)習(xí)這本書,去深挖一門技術(shù)??赐暌槐?,還有一遍,如此反復(fù)。發(fā)布出來是因為,技術(shù)需要交流,如果我有寫錯的地方,希望朋友們可以狠狠批評,我認真改過。好,廢話不多說,馬上開始頭腦風(fēng)暴。
上下文切換,時間片,線程調(diào)度算法
1 什么是上下文切換,上下文切換的原理是什么?
1.1 cpu調(diào)度算法干了什么?
在操作系統(tǒng)層面,有一個概念叫模式切換,從用戶態(tài)到內(nèi)核態(tài),或者從內(nèi)核態(tài)到用戶態(tài)。一般上下文切換指的是進程上下文切換,或者線程上下文切換。因為進程運行過程當(dāng)中,會產(chǎn)生,局部變量,進程的方法棧,當(dāng)cpu調(diào)度系統(tǒng),從一個時間片切換到另一個有效的時間片時,會保存現(xiàn)場數(shù)據(jù)(上下文數(shù)據(jù)),局部變量表,程序運行到哪一行,進入PC(程序計數(shù)器)等一些數(shù)據(jù)到內(nèi)核進程的堆棧中。 實際上,是因為cpu內(nèi)部有一個計數(shù)器,當(dāng)計數(shù)器倒計時到0,那么代表時間片用完了,這個時候,會產(chǎn)生一個中斷去觸發(fā),cpu調(diào)度算法,去重新分配時間片給在就緒隊列中的進程。如此循環(huán)往復(fù)。
1.2 CPU內(nèi)部的MMU干了什么?
1.2.1首先MMU的主要功能是什么?
- 地址轉(zhuǎn)換
- 權(quán)限管理
- 交換分區(qū)
首先引入兩個概念,虛擬地址(VA)和物理地址(Virtual Address , Physical Address)。如果處理器沒有MMU,或者有MMU但沒有啟用,CPU執(zhí)行單元發(fā)出的內(nèi)存地址將直接傳到芯片引腳上,被內(nèi)存芯片(以下稱為物理內(nèi)存,以便與虛擬內(nèi)存區(qū)分)接收,這稱為物理地址(Physical Address,以下簡稱PA)如果處理器啟用了MMU,CPU執(zhí)行單元發(fā)出的虛擬地址將被MMU映射轉(zhuǎn)換成PA。那么中間肯定有一個像對照表一樣的東西,從VA到PA。如果是32位處理器,那么他的地址總線是32位的,那么代表他的尋址能力是4G。但是實際上,這個表是存在于CPU內(nèi)部一個叫TLB(Translation Lookaside Buffer 一般叫頁表緩存,這個是頁表緩存,就有命中hit和miss,在物理內(nèi)存的內(nèi)核區(qū)域還有完整的頁表)結(jié)構(gòu)如下圖所示:
image.png
那么他不可能是這么大的一個表,肯定空間不夠。那他如何尋址?
答案是通過將內(nèi)存分頁。通過頁幀號+頁內(nèi)偏移來尋址,一般32位處理器是一頁32KB。尋址方式 如下圖。

然后回到原先的問題,MMU干了什么?在進程切換的時候,這里他要清空一些TLB中的映射條目,因為從一個虛擬地址空間到另外一個地址空間。有些條目,已經(jīng)失效了。但是內(nèi)核進程由于被用戶進程鎖共享,一直在內(nèi)存的某個區(qū)域不動,所以這部分的話,不需要刷新。cpu緩存中的數(shù)據(jù),也要被清空,設(shè)置為invaliad。這就導(dǎo)致,進程切換的時候,很多東西都是未命中,需要重新加載。切換時圖片如下所示:

2 疑問?
什么時候會觸發(fā)上下文切換? (這里的疑問??)是和進程一樣的吧,時間片到了我的猜測,只有在系統(tǒng)時鐘周期到了,或者由于中斷,陷入內(nèi)核態(tài),才會觸發(fā)調(diào)度算法。
《并發(fā)編程的藝術(shù)》書中顯示:
多線程競爭鎖時,會引起上下文切換
3 如何減少上下文切換
- CAS,無鎖并發(fā)編程。
- CLH 隊列,采用
喚醒傳遞機制,避免大量線程同時喚醒,引起一個驚群效應(yīng) - 分段鎖,或者鎖的離散化 ,就像jdk1.7 的ConcurrentHashMap .采用更強的離散函數(shù)。
- 在一定量的任務(wù)前提下,合理使用線程池。使得在用的線程得到更大程度的利用。比如
工作竊取fork/join,線程的重復(fù)利用。雖然說本身并不能減少上下文切換,但是參數(shù)上下文切換的線程少了,那就間接的減少了上下文切換。 - 協(xié)程。
3.1 協(xié)程是什么?
線程不同狀態(tài)之間的轉(zhuǎn)化是誰來實現(xiàn)的呢?是JVM嗎?
并不是。JVM需要通過操作系統(tǒng)內(nèi)核中的TCB(Thread Control Block)模塊來改變線程的狀態(tài),這一過程需要耗費一定的CPU資源。
多線程編程的耗性能操作:
- 同步鎖
- 線程的狀態(tài)切換
- 線程的上下文切換
但是,協(xié)程大大減少了上面所涉及的性能消耗。
協(xié)程,英文Coroutines,是一種比線程更加輕量級的存在。
協(xié)程不是被操作系統(tǒng)內(nèi)核所管理,而完全是由程序所控制(也就是在用戶態(tài)執(zhí)行)。
這樣帶來的好處就是性能得到了很大的提升,不會像線程切換那樣消耗資源。
3.2 協(xié)程的應(yīng)用
- Python語言
python可以通過 yield/send 的方式實現(xiàn)協(xié)程。在python 3.5以后,async/await 成為了更好的替代方案。 - Go語言
Go語言對協(xié)程的實現(xiàn)非常強大而簡潔,可以輕松創(chuàng)建成百上千個協(xié)程并發(fā)執(zhí)行。
