捲毛0基礎學習Golang-併發程式設計,01 什麼是併發

2020-08-10 16:47:21

捲毛0基礎學習Golang-併發程式設計,什麼是併發

Go併發程式設計

概述

簡而言之,所謂併發程式設計是指在一臺處理器上「同時」處理多個任務。

隨着硬體的發展,併發程式變得越來越重要。Web伺服器會一次處理成千上萬的請求。平板電腦和手機app在渲染使用者畫面同時還會後台執行各種計算任務和網路請求。即使是傳統的批次處理問題–讀取數據,計算,寫輸出–現在也會用併發來隱藏掉I/O的操作延遲以充分利用現代計算機裝置的多個核心。計算機的效能每年都在以非線性的速度增長。

宏觀的併發是指在一段時間內,有多個程式在同時執行。

併發在微觀上,是指在同一時刻只能有一條指令執行,但多個程式指令被快速的輪換執行,使得在宏觀上具有多個進程同時執行的效果,但在微觀上並不是同時執行的,只是把時間分成若幹段,使多個程式快速交替的執行。

在这里插入图片描述
併發(concurrency):

指在同一時刻只能有一條指令執行,但多個進程指令被快速的輪換執行,使得在宏觀上具有多個進程同時執行的效果,但在微觀上並不是同時執行的,只是把時間分成若幹段,通過cpu時間片輪轉使多個進程快速交替的執行。

在这里插入图片描述
以咖啡機舉例子
在这里插入图片描述

  • 兩個佇列同時使用兩臺咖啡機 (真正的多工)
  • 併發是兩個佇列交替使用一臺咖啡機 ( 假 的多工),只是這個交替速度飛快,在我們眼裏,就是好像一臺咖啡機能夠處理兩個佇列任務。

常見併發程式設計技術

進程併發

程式,是指編譯好的二進制檔案,在磁碟上,不佔用系統資源(cpu、記憶體、開啓的檔案、裝置、鎖…)

進程,是一個抽象的概念,與操作系統原理聯繫緊密。進程是活躍的程式,佔用系統資源。在記憶體中執行。(程式執行起來,產生一個進程)

進程狀態

進程基本的狀態有5種。分別爲初始態,就緒態,執行態,掛起態與終止態。其中初始態爲進程準備階段,常與就緒態結合來看。

在这里插入图片描述

在使用進程 實現併發時會出現什麼問題呢?
1.系統開銷比較大,佔用資源比較多,開啓進程數量比較少。
2. 在unix/linux系統下,還會產生「孤兒進程」和「殭屍進程」。

通過前面檢視操作系統的進程資訊,我們知道在操作系統中,可以產生很多的進程。在unix/linux系統中,正常情況下,子進程是通過父進程fork建立的,子進程再建立新的進程。

並且父進程永遠無法預測子進程 到底什麼時候結束。 當一個 進程完成它的工作終止之後,它的父進程需要呼叫系統呼叫取得子進程的終止狀態。

  • 孤兒進程
    孤兒進程: 父進程先於子進程結束,則子進程成爲孤兒進程,子進程的父進程成爲init進程,稱爲init進程領養孤兒進程。
  • 殭屍進程
    殭屍進程: 進程終止,父進程尚未回收,子進程殘留資源(PCB)存放於內核中,變成殭屍(Zombie)進程。

執行緒併發

什麼是執行緒

LWP:light weight process 輕量級的進程,本質仍是進程 (Linux下)

進程:獨立地址空間,擁有PCB

執行緒:有獨立的PCB,但沒有獨立的地址空間(共用)

區別:在於是否共用地址空間。獨居(進程);合租(執行緒)。

  1. 執行緒:最小的執行單位
  2. 進程:最小分配資源單位,可看成是隻有一個執行緒的進程。

Windows系統下,可以直接忽略進程的概念,只談執行緒。因爲執行緒是最小的執行單位,是被系統獨立排程和分派的基本單位。而進程只是給執行緒提供執行環境。

在这里插入图片描述

執行緒同步

同步即協同步調,按預定的先後 先後次序執行。

執行緒同步,指一個執行緒發出某一功能呼叫時,在沒有得到結果之前,該呼叫不返回。同時其它執行緒爲保證數據一致性,不能呼叫該功能。

舉例: 記憶體中100位元組,執行緒T1欲填入全1, 執行緒T2欲填入全0。但如果T1執行了50個位元組失去cpu,T2執行,會將T1寫過的內容覆蓋。當T1再次獲得cpu繼續 從失去cpu的位置向後寫入1,當執行結束,記憶體中的100位元組,既不是全1,也不是全0。

產生的現象叫做「與時間有關的錯誤」(time related)。爲了避免這種數據混亂,執行緒需要同步。

「同步」的目的,是爲了避免數據混亂,解決與時間有關的錯誤。實際上,不僅執行緒間需要同步,進程間、信號間等等都需要同步機制 機製。

因此,所有「多個控制流,共同操作一個共用資源」的情況,都需要同步。

鎖的應用

互斥量mutex

Linux中提供一把互斥鎖mutex(也稱之爲互斥量)。

每個執行緒在對資源操作前都嘗試先加鎖,成功加鎖才能 纔能操作,操作結束解鎖。

通過「鎖」就將資源的存取變成互斥操作,而後與時間有關的錯誤也不會再產生了。

在这里插入图片描述
但,應注意:同一時刻,只能有一個執行緒持有該鎖。

當A執行緒對某個全域性變數加鎖存取,B在存取前嘗試加鎖,拿不到鎖,B阻塞。C執行緒不去加鎖,而直接存取該全域性變數,依然能夠存取,但會出現數據混亂。

所以,互斥鎖實質上是操作系統提供的一把「建議鎖」(又稱「協同鎖」),建議程式中有多執行緒存取共用資源的時候使用該機制 機製。但,並沒有強制限定。

因此,即使有了mutex,如果有執行緒不按規則來存取數據,依然會造成數據混亂。

讀寫鎖

與互斥量類似,但讀寫鎖允許更高的並行性。其特性爲:寫獨佔讀共用

  • 讀寫鎖特性:
  1. 讀寫鎖是「寫模式加鎖」時, 解鎖前,所有對該鎖加鎖的執行緒都會被阻塞。
  2. 讀寫鎖是「讀模式加鎖」時, 如果執行緒以讀模式對其加鎖會成功;如果執行緒以寫模式加鎖會阻塞。
  3. 讀寫鎖是「讀模式加鎖」時, 既有試圖以寫模式加鎖的執行緒,也有試圖以讀模式加鎖的執行緒。那麼讀寫鎖會阻塞隨後的讀模式鎖請求。優先滿足寫模式鎖。讀鎖、寫鎖並行阻塞,寫鎖優先順序高

讀寫鎖也叫共用-獨佔鎖。當讀寫鎖以讀模式鎖住時,它是以共用模式鎖住的;當它以寫模式鎖住時,它是以獨佔模式鎖住的。寫獨佔、讀共用。

讀寫鎖非常適合於對數據結構讀的次數遠大於寫的情況。

協程併發

協程:coroutine。也叫輕量級執行緒。

協程最大的優勢在於「輕量級」。可以輕鬆建立上萬個而不會導致系統資源衰竭。而執行緒和進程通常很難超過1萬個。這也是協程別稱「輕量級執行緒」的原因。

一個執行緒中可以有任意多個協程,但某一時刻只能有一個協程在執行,多個協程分享該執行緒分配到的計算機資源。

在協程中,呼叫一個任務就像呼叫一個函數一樣,消耗的系統資源最少!但能達到進程、執行緒併發相同的效果。

在一次併發任務中,進程、執行緒、協程均可以實現。從系統資源消耗的角度出發來看,進程相當多,執行緒次之,協程最少。

Go併發

Go 在語言級別支援協程,叫goroutine。Go 語言標準庫提供的所有系統呼叫操作(包括所有同步IO操作),都會出讓CPU給其他goroutine。這讓輕量級執行緒的切換管理不依賴於系統的執行緒和進程,也不需要依賴於CPU的核心數量。

Go從語言層面就支援並行。同時,併發程式的記憶體管理有時候是非常複雜的,而Go語言提供了自動垃圾回收機制 機製。

Go語言中的併發程式主要使用兩種手段來實現。goroutinechannel