30 張圖解 | 高頻面試知識點總結:面試官問我高併發服務模型哪家強?

文章每週持續更新,原創不易,「三連」讓更多人看到是對我最大的確定。能夠微信搜索公衆號「 後端技術學堂 」第一時間閱讀(通常比博客早更新一到兩篇)web

面試中常常會被問到高性能服務模型選擇對比,以及如何提升服務性能和處理能力,這其中涉及操做系統軟件和計算機硬件知識,其實都是在考察面試者的基礎知識掌握程度,但若是沒準備的話容易一頭霧水,此次帶你們從頭至尾學習一遍,學完這一篇不再怕面試官刨根問底了!面試

任務類型

談高併發服務模型選擇以前,咱們先來看下程序的的任務類型,程序任務類型通常分爲 CPU 密集型任務和 IO 密集型任務,這兩種任務有各自的特色,對程序的要求是不同的須要分開對待。數據庫

CPU密集型任務

一個程序任務大部分是計算類的,好比邏輯處理、數值比較和計算,咱們就稱它是 CPU 密集型任務或計算密集型任務。CPU 密集型任務的特色是要進行大量的計算,消耗 CPU 資源,好比計算圓周率、視頻編解碼這些靠的是 CPU 的運算能力。編程

CPU 密集型任務雖然也能夠用多任務完成,可是任務越多,任務之間切換的時間就越多,CPU 執行效率反而更低,因此要最高效地利用 CPU,任務並行數應當等於 CPU 的核心數,避免任務在 CPU 核之間頻繁切換。windows

芯片線路
芯片線路

IO密集型任務

一個程序涉及到大量網絡、磁盤等比較耗時的輸入輸出任務,就稱它是 IO 密集型任務,這類任務的特色是 CPU 消耗不多,任務的大部分時間都在等待 IO 操做完成(由於 IO 的速度遠遠低於 CPU 和內存的速度,不是一個數量級的)。後端

對於 IO 密集型任務,任務越多 CPU 效率越高,但也不是無限的開啓多任務,若是任務過多頻繁切換的開銷也不可忽視。常見的大部分程序都是執行 IO 密集型任務,好比互聯網業務的 Web 服務,數據庫操做等。api

五彩的以太網口
五彩的以太網口

服務模型

無論是 CPU 密集型任務仍是 IO 密集型任務,要提升服務器處理能力,能夠從軟件和硬件兩個層面來作文章。緩存

先說軟件層面,單個任務處理能力有限,能夠經過啓動多個功能徹底相同的服務實例,藉此來提升服務總體處理性能,多服務實例的實現主流的技術有三種:多進程、多線程、多協程。固然除了用多實例的方式,還有 IO 多路複用、異步 IO 等技術,爲了文章主題明確,不在本文展開討論。服務器

服務模型哪家強

既然有三種技術實現,那麼你可能會問,在三個模型裏選一個最好的來實現服務,該如何選擇一個適合的服務模型呢?微信

抱歉,小孩子才作選擇我全都要!哈哈,開個玩笑。

答案是沒有最好,服務模型選擇要結合自身服務處理的任務類型。任務類型就是咱們上面說的 CPU 密集型和 IO 密集型,只有清楚的知道所處理業務的任務類型,才能在上述服務模型中選擇其一或多種模型組合,來搭建適合你的高性能服務框架。

多進程服務模型

進程概念

程序是一些保存在磁盤上的指令的有序集合,是靜態的。進程是程序執行的過程,包括了動態建立、調度和消亡的整個過程,進程是程序資源管理的最小單位

多進程模型

多進程模型是啓動多個服務進程。原來由一個進程作的事,當一個進程忙不過來,建立幾個功能同樣的進程來幫它一塊兒幹活,人多力量大。

因爲多進程地址空間不一樣,數據不能共享,一個進程內建立的變量在另外一個進程是沒法訪問。操做系統看不下去了,憑什麼同在一臺機器,彼此相愛的兩個進程不能說說話呢?

因而操做系統提供了各類系統調用,搭建起各個進程間通訊的橋樑,這些方法統稱爲進程間通訊 IPC (IPC InterProcess Communication)

常見進程間通訊方式

管道 Pipe

管道的實質是一個內核緩衝區,進程以先進先出 FIFO 的方式從緩衝區存取數據。 是一種半雙工的通訊方式,數據只能單向流動,並且只能在具備親緣關係(父子進程間)的進程間通訊。

管道工做原理

  1. 管道一端的進程順序的將數據寫入緩衝區,另外一端的進程則順序的讀出數據。

  2. 緩衝區能夠看作是一個循環隊列,一個數據只能被讀一次,讀出來後在緩衝區就不復存在了。

  3. 當緩衝區爲讀空或寫滿,讀數據的進程或寫數據進程進入等待隊列。

  4. 空的緩衝區有新數據寫入,或者滿的緩衝區有數據讀出時,喚醒等待隊列中的進程繼續讀寫。

管道圖解
管道圖解

命名管道 FIFO

上面介紹的管道也稱爲匿名管道,只能用於親緣關係的進程間通訊。爲了克服這個缺點,出現了有名管道 FIFO 。有名管道提供了一個路徑名與之關聯,以文件形式存在於文件系統中,這樣即便不存在親緣關係的進程,只要能夠訪問該路徑也能相互通訊。

命名管道支持同一臺計算機的不一樣進程之間,可靠的、單向或雙向的數據通訊。 FIFO圖解.png

信號 Signal

信號是Linux系統中用於進程間互相通訊或者操做的一種機制,信號能夠在任什麼時候候發給某一進程,無需知道該進程的狀態。若是該進程當前不是執行態,內核會暫時保存信號,當進程恢復執行後傳遞給它。

若是一個信號被進程設置爲阻塞,則該信號的傳遞被延遲,直到其阻塞被取消是才被傳遞給進程。

信號在用戶空間進程和內核之間直接交互,內核能夠利用信號來通知用戶空間的進程發生了哪些系統事件,信號事件主要有兩個來源:

  • 硬件來源:用戶按鍵輸入 Ctrl+C退出、硬件異常如無效的存儲訪問等。
  • 軟件終止:終止進程信號、其餘進程調用 kill 函數、軟件異常產生信號。

消息隊列 Message Queue

消息隊列是存放在內核中的消息鏈表,每一個消息隊列由消息隊列標識符表示, 只有在內核重啓或主動刪除時,該消息隊列纔會被刪除。

消息隊列是由消息的鏈表,存放在內核中並由消息隊列標識符標識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩衝區大小受限等缺點。 另外,某個進程往一個消息隊列寫入消息以前,並不須要另外讀進程在該隊列上等待消息的到達。 消息隊列圖解

共享內存 Shared memory

共享內存是一個進程把地址空間的一段,映射到能被其餘進程所訪問的內存,一個進程建立、多個進程可訪問,進程就能夠直接讀寫這一塊內存而不須要進行數據的拷貝,從而大大提升效率。

共享內存使得多個進程能夠能夠直接讀寫同一塊內存空間,是最快的可用 IPC 形式,是針對其餘通訊機制運行效率較低而設計的。共享內存每每與其餘通訊機制,如信號量配合使用,來實現進程間的同步和互斥通訊。

共享內存
共享內存

套接字 Socket

套接字你可能沒聽過這個名字,但絕對是接觸的最多的一種進程間通訊方式。由於咱們熟悉的 TCP/IP 協議棧,也是創建在 socket 通訊之上,TCP/IP 構建起了當前的互聯網通訊網絡。

它是一種通訊機制,憑藉這種機制,既能夠在本機進程間通訊,也能夠跨網絡經過,由於,套接字經過網絡接口將數據發送到本機的不一樣進程或遠程計算機的進程。

socket套接字
socket套接字

多線程服務模型

線程概念

線程是操做操做系統可以進行運算調度的最小單位。線程被包含在進程之中,是進程中的實際運做單位,一個進程內能夠包含多個線程,線程是資源調度的最小單位。 進程線程關係

多線程模型

啓動多個相同功能的進程能提升服務處理能力,但因爲各個進程的地址空間相互隔離,通訊不便。

因而,多線程服務模型出場。經過前面的學習咱們知道,一個進程內的多個線程能夠共享進程的所有系統資源。進程內建立的多個線程均可以訪問進程內的全局變量。

固然沒有免費的午飯,線程雖然能方便的訪問進程資源,但也帶來了額外的問題。好比多線程訪公共資源帶來的同步與互斥問題,不一樣線程訪問資源的前後順序會相互影響,若是不作好同步和互斥會產生預期以外的結果,甚至死鎖。

什麼是多線程同步

多線程同步是線程之間的一種直接制約關係,一個線程的執行依賴另外一個線程的通知,當它沒有獲得另外一個線程的通知時必須等待,直到消息到達時才被喚醒,即有很強的執行前後關係。

好比你搭建了一個商城服務。這個服務的下單流程是這樣的:第一步必需要先挑選商品加入購物車,第二步才能結帳計算訂單金額,假設這兩個步驟的操做分別由兩個線程去完成,則這兩個線程的操做順序很重要,必須是先下單再結帳,這就是線程同步。 購物車

什麼是多線程互斥

多線程互斥指的是多線程對資源訪問的排他性。所謂排他性,就是當有多個線程都要使用某一共享資源時,任什麼時候刻最多隻容許一個線程得到對這個共享資源的使用權,當共享資源被其中一個線程佔有時,其餘未得到資源的線程必須等待,直到佔用資源的線程釋放資源。

打個比方,大家班只有一臺投影儀,當一個同窗在上面放電影的時候,若是老師進來上課要用這個投影儀,那就只能由這個同窗放棄投影儀的使用權,交給老師上課投影使用,對,教室裏惟一的投影儀是共享資源,具備排他性,老師和學生比做是兩個線程的話,那這兩個線程是互斥的訪問共享資源(投影儀)。

投影儀
投影儀

多線程同步和互斥方法

Linux 系統提供如下幾種方法來解決多線程的同步和互斥問題,分別是:互斥鎖、條件變量、讀寫鎖、自旋鎖、條件變量。

互斥鎖(同步)

互斥鎖的做用是對臨界區加以保護,以使任意時刻只有一個線程可以執行臨界區的代碼,實現了多線程對臨界資源的互斥訪問。

互斥鎖接口函數:

互斥鎖api
互斥鎖api

條件變量(同步)

條件變量是用來等待而不是用來上鎖的。條件變量用來自動阻塞一個線程,直到某特殊狀況發生爲止。適合多個線程等待某個條件的發生,不使用條件變量,那麼每一個線程就不斷嘗試互斥鎖並檢測條件是否發生,浪費系統資源

一般條件變量和互斥鎖同時使用。條件的檢測是在互斥鎖的保護下進行的。若是一個條件爲假,一個線程自動阻塞,並釋放等待狀態改變的互斥鎖。若是另外一個線程改變了條件,它發信號給關聯的條件變量,喚醒一個或多個等待它的線程,從新得到互斥鎖,從新評價條件,能夠用來實現線程間的同步。

條件變量系統 API 以下:

條件變量API
條件變量API

讀寫鎖(同步)

互斥量要麼是加鎖狀態,要麼是不加鎖狀態,並且一次只有一個線程對其進行加鎖。讀寫鎖能夠有3種狀態:讀加鎖狀態、寫加鎖狀態和不加鎖狀態

一次只有一個線程能夠佔有寫模式讀寫鎖,可是能夠有多個線程同時佔有讀模式的讀寫鎖。所以,讀寫鎖適合於對數據結構的讀次數比寫次數多得多的狀況,且讀寫鎖比互斥量具備更高的並行性。

讀寫鎖加鎖規則

1:若是某線程申請了讀鎖,其它線程能夠再申請讀鎖,但不能申請寫鎖;

2:若是某線程申請了寫鎖,其它線程不能申請讀鎖,也不能申請寫鎖。

讀寫鎖系統 API

讀寫鎖API
讀寫鎖API

自旋鎖(同步)

互斥鎖得不到鎖時,線程會進入休眠,引起任務上下文切換,任務切換涉及一系列耗時的操做,所以用互斥鎖一旦遇到阻塞切換代價是十分昂貴的。

而自旋鎖阻塞後不會引起上下文切換,當鎖被其餘線程佔有時,獲取鎖的線程便會進入自旋,不斷檢測自旋鎖的狀態,直到獲得鎖,所謂的自旋就是循環等待的意思。

自旋鎖在用戶態使用的比較少,在內核使用的比較多。自旋鎖適用於臨界區代碼比較短,鎖的持有時間比較短的場景,不然會讓其餘線程一直等待形成飢餓現象。

自旋鎖 API 接口

自旋鎖API
自旋鎖API

信號量(同步與互斥)

信號量本質上是一個非負的整數計數器,它被用來控制對公共資源的訪問。

信號量是一個特殊類型的變量,它能夠被增長或者減小。可根據操做信號量值的結果判斷是否對公共資源具備訪問的權限,當信號量值大於 0 時,則能夠訪問,不然將阻塞。但對其的訪問被保證是原子操做,即便在一個多線程程序中也是如此。

信號量類型:

  • 二進制信號量,它只有0和1兩種取值。適用於臨界代碼每次只能被一個執行線程運行,就要用到二進制信號量。

  • 計數信號量。它能夠有更大的取值範圍,適用於臨界代碼容許有限數目的線程執行,就須要用到計數信號量。

信號量 API

信號量API
信號量API

協程服務模型

什麼是協程

什麼是協程呢?協程 Coroutines 是一種比線程更加輕量級的微線程。類比一個進程能夠擁有多個線程,一個線程也能夠擁有多個協程,所以協程又稱微線程和纖程。

協程圖解
協程圖解

能夠粗略的把協程理解成子程序調用,每一個子程序均可以在一個單獨的協程內執行。

協程子程序模型
協程子程序模型

協程服務模型

爲了說明什麼是協程模型,先用多線程下的生產者消費者模型舉個栗子。

啓動兩個線程分別執行兩個函數 Do_some_IODo_some_process ,第一個作耗時的 IO 處理操做,第二個對 IO 操做結果作快速的處理計算工做。僞代碼以下:

函數僞代碼
函數僞代碼

多線程執行過程是這樣的:

  1. 生產者線程先調用函數 Do_some_IO 作比較耗時的 IO 操做,好比從網絡套接字中讀取數據這類操做。

  2. 在生產者線程執行 Do_some_IO 完成數據讀取以前,消費者線程要阻塞等待。

  3. 在消費者線程執行 Do_some_process 完成數據處理完成以前,生產者線程要阻塞等待。

  4. 在消費者線程執行 Do_some_process 完成數據處理完成以後,要通知生成者線程繼續 Do_some_IO
    多線程執行模型.png

能夠看到,多線程模型爲了保證各個線程並行工做,須要額外作不少線程間的同步和通知工做,並且線程頻繁的在阻塞和喚醒間切換,咱們知道 Linux 下線程是輕量級線程 LWP ,每次線程切換涉及用戶態和內核態的切換,仍是很消耗性能的。

一樣的場景在協程模型裏是怎麼處理的呢?仍是用前面的例子,說明協程模型的執行流程。

Do_some_IO()       // IO處理協程
Do_some_process() // 計算處理協程 複製代碼
  1. 分配生產者協程執行 Do_some_IO 作 IO 處理操做,分配消費者協程執行 Do_some_process 計算處理操做。
  2. 在生產者協程工做期間,消費者協程保持等待。
  3. 當生產者協程完成 IO 處理,返回處理結果給消費者,並把程序執行權限交給消費者協程向下執行。
協程執行時間線.png
協程執行時間線.png

協程優點

  • 因爲協程在線程內實現,所以始終都是一個線程操做共享資源,因此不存在多線程搶佔資源和資源同步問題。

  • 生產者協程和消費者協程,互相配合協做完成工做,而不是相互搶佔,並且協程建立和切換的開銷比線程小得多。

硬件提高性能

前面講的多線程、多進程、協程都還只是軟件層面的提升服務處理能力。真正硬核的是從硬件層面提升處理能力,增長 CPU 物理核心數目,固然硬件都是有成本的,因此只有軟件層面已經充分榨乾性能纔會考慮增長硬件。

不過,老闆有錢買最好最貴的服務器另說,這是人民幣玩家和窮逼玩家的區別了,軟件工程師留下了貧困的淚水。

增長機器核心數

CPU領域有一條摩爾定律:大概 18 個月會將芯片的性能提升一倍。如今這個定律變的愈來愈難以突破,CPU 晶體管密度工做頻率很難再提升,轉而經過增長 CPU 核心數目的方式提升處理器性能。

cpu
cpu

目前商用服務器架構基本都是多核處理器,多核的處理器可以真正作到程序並行運行,處理效率大幅度提高,那該如何查看 CPU 核心數目呢?

對於 Windows 操做系統,打開任務管理器,經過界面的「內核」和「邏輯處理器」能看到。

windows 查看核心
windows 查看核心

查看 cpu 核心數

對於 Linux 操做系統,經過下面 2 種方式查看 CPU 核心相關信息。

1. 經過cpuinfo文件查看

使用cat /proc/cpuinfo查看 cpu 核心信息,以下兩個信息:

  • processor,指明第幾個cpu處理器
  • cpu cores,指明每一個處理器的核心數

cpuinfo 輸出示例:

cpuinfo
cpuinfo

2. 經過編程接口查看

除了上面以文件的形式查看 cpu 核心信息以外,系統還提供了編程接口能夠查詢,系統 API 以下。

查看核數API
查看核數API

CPU親和性

CPU 親和性是綁定某一進程或線程到特定的 CPU 或 CPU 集合,從而使得該進程或線程只能被調度運行在綁定的 CPU或 CPU 集合上。

爲何要設置 CPU 親和性綁定 CPU 呢?理論上進程上一次運行後的上下文信息會保留在 CPU 的緩存中,若是下一次仍然將該進程調度到同一個 CPU 上,就能避免緩存未命中對 CPU 處理性能的影響,從而使得進程的運行更加高效。

假如某些進程或線程是 CPU 密集型的,不但願被頻繁調度,又或者你有其餘特殊需求,不但願進程或線程被調度在不一樣 CPU 之間頻繁切換,則能夠將該進程或線程綁定到特定的 CPU 上 ,能夠在特定場景下優化程序性能。

綁定進程

在多進程模型中,綁定進程到特定的核心,下面是綁定進程的系統 API 設置進程親和性

綁定線程

在多線程模型中,綁定線程到特定的核心,下面是綁定線程的系統 API

設置線程親和性
設置線程親和性

總總結結

本文從程序任務類型出發,區分任務爲 CPU 密集型和 IO 密集型兩大類。接着分別說明提升基於這兩類任務的服務性能方法,分爲軟件層面的方法和硬件層面的方法,其中軟件層面主要講述利用多進程、多線程以及協程模型,固然現有的技術還有 IO 多路複用、異步 IO 、池化技術等方案。講到多線程和多進程,順勢說明了進程間通訊和線程間同步互斥技術。

第二部分,講解了從硬件層面提升服務性能:提升機器核心數,並教你如何查看 CPU 核心數的方法。最後,還能夠經過軟硬結合的方式,把硬件核心綁定到指定進程或者線程執行,最大程度的利用 CPU 性能。

但願經過本文的學習,讀者對高性能服務模型有個初步的瞭解,並能對服務優化的方法和利弊舉例一二,就是本文的價值所在。

再聊兩句(求個三連)

感謝各位的閱讀,文章的目的是分享對知識的理解,技術類文章我都會反覆求證以求最大程度保證準確性,若文中出現明顯紕漏也歡迎指出,咱們一塊兒在探討中學習。

若是以爲文章寫的還行,對你有所幫助,不要白票 lemon,動動手指「點贊」「三連」是對我持續創做的最大支持。

今天的技術分享就到這裏,咱們下期再見。

能夠微信搜索公衆號「 後端技術學堂 」回覆「資料」「1024」有我給你準備的各類編程學習資料。文章每週持續更新,咱們下期見!

相關文章
相關標籤/搜索