最近在翻閱文章時,看到朋友推薦的《程序員的自我修養》,這是一本講連接、裝載與庫的計算機圖書,看了下目錄後以爲挺有意思。程序員
所以決定每讀一章就將其讀書筆記整理記錄下來,分享給你們。網絡
目錄:多線程
在計算機發展早期,CPU 資源十分昂貴。若是一個 CPU 只能運行一個程序,那麼當程序在讀寫磁盤(進行 I/O 操做)時,CPU 就空閒下來了。這在當時簡直就是巨大的浪費。併發
CPU 只能和一個程序A 「聊天「,其餘來再多的程序BCD,都沒有任何操做的空間。就像早年的手機,打電話和上網(語音/數據)只能二選一,做爲 CPU 的你,並不能多線程操做。性能
所以機智的人們很快就編寫了一些監控程序,但願來解決這個問題。spa
多道程序起,操做系統正式具備同時運行多個程序的能力。操作系統
其是讓 CPU 一次讀取多個程序放入內存中。當某個程序暫時無須使用 CPU 時,監控程序就把另外的正在等待 CPU 資源的程序啓動,以此使得 CPU 可以充分地利用起來。這種策略的確大大的提升了 CPU 資源的利用率。線程
你在 Windows 上點擊鼠標 10 分鐘之後系統纔有反應,那是多麼無奈的事情。由於沒有優先級區分,天然一路排下來也就不知道要等到何時了,至關於半餓死。設計
核心問題在於程序之間的調度策略太粗糙。對於多道程序來刪,程序之間部分輕重緩急,也就是說不存在優先級的區分。所以若是有些程序急需使用 CPU 來完成一些任務,那麼頗有可能會很長時間後纔有機會被分配到 CPU,才得以繼續往下運行。3d
程序運行模式改成協做的模式,在原有的多道程序繼續升級改造,即每一個程序運行一段時間之後都主動讓出 CPU 給其餘程序,使得一段時間內每一個程序都有機會運行一小段。
好比你點擊一下鼠標或按下一個鍵盤按鍵後,他會相較前者可以更快的獲得響應,由於他好歹是存在切換的可能性。
這時候的監控程序已經比原有多道程序的模式已經複雜了很多,完整的操做系統雛形已經基本造成,很早期的 Windows(Windows 95 和 Windows NT 以前),MacOS X 以前的 MacOS 版本都是採用這種分時系統的方式來進行程序調度。
其仍然存在問題,核心在於若一個程序一直在進行一個耗時計算,便會一直霸佔着 CPU 不放,那麼操做系統也沒有不放,就會致使其餘程序都只能無限等待,至關於就是系統假死了。
在分時系統中,一個程序死循環就會致使系統假死,而且其運行效率並不高,只能解決當時的交互式環境。
放在如今來說,已經徹底無法很好的運行。所以當時業界也在研究更爲先進的操做系統模式,也就是如今最爲流行也是最熟悉的多任務系統。
在多任務系統中,全部的應用程序都以進行(Process)的方式運行,其有如下特色:
但須要注意的是,如果進程運行超出了指定的時間,操做系統就會暫停該進程,將 CPU 資源分配給其餘等待運行的進程。這種 CPU 的分配方式通常稱做搶佔式(Preemptive)。
經過這種方式,操做系統就能夠強制剝奪 CPU 資源而且分配給它認爲目前最須要資源的進程,若是分配給每一個進程的時間都很短,即 CPU 在多個進程間快速切換,就能夠形成多個進程同時在運行的假象。
在早期的計算機中,程序是直接運行在物理內存上的,訪問的內存地址都是物理地址。假設只是一個進程在跑,可能內存資源還夠用,但實際上爲了更有效地利用硬件資源,咱們必須運行多個程序,CPU 的利用率纔會比較高。這時候就會遇到一個嚴重的問題,那就是如何將計算機上有限的物理內存分配給多個程序使用?
就像上圖,每一個程序他都想申請 1GB 的內容,而計算機自己只有 1GB 的物理內存,根本沒有辦法真正的執行。
可能會有小夥伴想,煎魚你舉的例子太極端了,咱們舉個 」正常「 點的例子。假設計算機有 128MB 內存,程序 A 運行須要 10MB,程序 B 須要 100MB,程序 C 須要 20 MB。假設該幾個程序運行時,咱們按照其想要的一分配,不就行了嗎?
但現實並非這樣,這種簡單的內存分配策略存在許多的問題:
解決上述問題的解決思路,就是萬能的法寶:增長中間層,即便用一種間接的地址訪問方法。把程序給出的地址看做是一種虛擬地址(Virtual Address),而後經過某些映射的方法,將這個虛擬地址最終轉換成實際的物理地址。
上述提到了兩個很是重要的內存概念:
如此一來,操做系統只須要控制虛擬地址到物理地址的映射過程,就能夠保證任意一個程序鎖你訪問的物理內存區域和另一個程序不重疊,以達到地址空間隔離的效果。
另外須要清楚虛擬存儲的實現須要依靠硬件的支持,對於不一樣的 CPU 來講不一樣。但大多采用 MMU(Memory Management Unit)的部件來進行頁映射:
CPU 發出的是虛擬地址(Virtual Address),也就是平常程序中所看到的是虛擬地址。通過 MMU 轉換後就會變成物理地址(Physical Address)。
目前常見的 MMU 均已集成在 CPU 內部了,不會再以獨立部件存在。
線程(Thread),有時候被稱爲輕量級進程,是程序執行流程的最小單元。一個標準的線程由線程 ID、當前指令指針(PC)、寄存器集合和堆棧組成。
一般一個進程由一個到多個線程組成,各個線程之間共享程序的內存空間(包括代碼段、數據段、堆等)及一些進程級的資源(如打開文件和信號)。
線程能夠訪問進程內存裏的全部數據,甚至在知道堆棧地址的狀況下,能夠訪問其餘線程裏的堆棧信息。其私有存儲空間主要分爲:棧、線程局部存儲(Thread Local Storage,TLS)、寄存器(包括 PC 寄存器)。
在單處理器對應多線程的狀況下,併發是一種模擬處理的狀態。操做系統會讓這些多線程程序輪流執行,每次僅執行一小段時間(一般是幾十到幾百毫秒),這樣子線程就 「看起來」 在同時執行。
不斷在處理器上切換不一樣的線程行爲稱之爲線程調度(Thread Schedule),一般擁有至少三種狀態,分別是:
處於運行狀態中的線程都會擁有一段能夠執行的時間,這段時間段稱爲時間片(Time Slice)。其基本流轉:
每當一個線程離開運行狀態時,調度系統就會選擇一個當前是就緒狀態的線程繼續執行。而一個處於等待狀態的線程在完成所等待的事件後,就會進入就緒狀態。
在 Windows 和 Linux 中,線程的優先級能夠經過用戶手動設置,系統也會根據線程的表現自動調整優先級,以使得調度更有效率。常見的通常有兩類線程:
常見的線程調度方式以下:
IO 密集型線程老是會比 CPU 密集型線程容易獲得優先級的提高。但在優先級調度下,存在一種線程餓死的現象。一個線程被餓死,是說它的優先級比較低,在它執行以前,老是有較高優先級的線程要執行。所以低優先級線程始終沒法執行。
爲了不餓死現象,調度系統會逐步提高那些等待了過長時間的得不到執行的線程優先級。這樣的方式,一個線程只要等待足夠長的時間,其優先級最終必定會提升到足夠讓他執行的程度。線程優先級改變通常有三種方式:
線程在用盡時間片以後會被強制剝奪繼續執行的權利,而進入就緒狀態,這個過程叫作搶佔(Preemption),即以後執行的別的線程搶佔了當前線程。
目前以可搶佔式線程居多,非搶佔式線程在今日已經十分少見。
平常在程序中使用的線程其實並非內核線程,而是存在於用戶態的用戶線程。用戶態並不必定在操做系統內核中對應同等數量的內核線程。接下來將介紹三種常見的用戶態多線程庫的實現方式。
一對一模型指的是一個用戶使用的線程就惟一對應一個內核使用的線程。
優勢:
缺點:
多對一模型指的是多個用戶線程映射到一個內核線程上,線程之間的切換由用戶態的代碼來進行。
優勢:
缺點:
多對多模型指的是將多個用戶線程映射到少數但不止一個內核線程上。
優勢:
缺點:
本文主要涉及到 CPU、內存、線程。咱們可以從其的一些關注點知道爲何 CPU 調度會這樣子發展,又經歷了什麼東西。內存爲何會出現虛擬內存,物理內存,其之間又是如何相互轉換的。
另外還了解到線程的基本分類和常見調度方式等,這些都是計算機基本的軟硬件知識,很是值得你們仔細思考。