概述
簡而言之,所謂併發程式設計是指在一臺處理器上「同時」處理多個任務。
隨着硬體的發展,併發程式變得越來越重要。Web伺服器會一次處理成千上萬的請求。平板電腦和手機app在渲染使用者畫面同時還會後台執行各種計算任務和網路請求。即使是傳統的批次處理問題–讀取數據,計算,寫輸出–現在也會用併發來隱藏掉I/O的操作延遲以充分利用現代計算機裝置的多個核心。計算機的效能每年都在以非線性的速度增長。
宏觀的併發是指在一段時間內,有多個程式在同時執行。
併發在微觀上,是指在同一時刻只能有一條指令執行,但多個程式指令被快速的輪換執行,使得在宏觀上具有多個進程同時執行的效果,但在微觀上並不是同時執行的,只是把時間分成若幹段,使多個程式快速交替的執行。
併發(concurrency):
指在同一時刻只能有一條指令執行,但多個進程指令被快速的輪換執行,使得在宏觀上具有多個進程同時執行的效果,但在微觀上並不是同時執行的,只是把時間分成若幹段,通過cpu時間片輪轉使多個進程快速交替的執行。
以咖啡機舉例子
程式,是指編譯好的二進制檔案,在磁碟上,不佔用系統資源(cpu、記憶體、開啓的檔案、裝置、鎖…)
進程,是一個抽象的概念,與操作系統原理聯繫緊密。進程是活躍的程式,佔用系統資源。在記憶體中執行。(程式執行起來,產生一個進程)
進程狀態
進程基本的狀態有5種。分別爲初始態,就緒態,執行態,掛起態與終止態。其中初始態爲進程準備階段,常與就緒態結合來看。
在使用進程 實現併發時會出現什麼問題呢?
1.系統開銷比較大,佔用資源比較多,開啓進程數量比較少。
2. 在unix/linux系統下,還會產生「孤兒進程」和「殭屍進程」。
通過前面檢視操作系統的進程資訊,我們知道在操作系統中,可以產生很多的進程。在unix/linux系統中,正常情況下,子進程是通過父進程fork建立的,子進程再建立新的進程。
並且父進程永遠無法預測子進程 到底什麼時候結束。 當一個 進程完成它的工作終止之後,它的父進程需要呼叫系統呼叫取得子進程的終止狀態。
什麼是執行緒
LWP:light weight process 輕量級的進程,本質仍是進程 (Linux下)
進程:獨立地址空間,擁有PCB
執行緒:有獨立的PCB,但沒有獨立的地址空間(共用)
區別:在於是否共用地址空間。獨居(進程);合租(執行緒)。
Windows系統下,可以直接忽略進程的概念,只談執行緒。因爲執行緒是最小的執行單位,是被系統獨立排程和分派的基本單位。而進程只是給執行緒提供執行環境。
同步即協同步調,按預定的先後 先後次序執行。
執行緒同步,指一個執行緒發出某一功能呼叫時,在沒有得到結果之前,該呼叫不返回。同時其它執行緒爲保證數據一致性,不能呼叫該功能。
舉例: 記憶體中100位元組,執行緒T1欲填入全1, 執行緒T2欲填入全0。但如果T1執行了50個位元組失去cpu,T2執行,會將T1寫過的內容覆蓋。當T1再次獲得cpu繼續 從失去cpu的位置向後寫入1,當執行結束,記憶體中的100位元組,既不是全1,也不是全0。
產生的現象叫做「與時間有關的錯誤」(time related)。爲了避免這種數據混亂,執行緒需要同步。
「同步」的目的,是爲了避免數據混亂,解決與時間有關的錯誤。實際上,不僅執行緒間需要同步,進程間、信號間等等都需要同步機制 機製。
因此,所有「多個控制流,共同操作一個共用資源」的情況,都需要同步。
Linux中提供一把互斥鎖mutex(也稱之爲互斥量)。
每個執行緒在對資源操作前都嘗試先加鎖,成功加鎖才能 纔能操作,操作結束解鎖。
通過「鎖」就將資源的存取變成互斥操作,而後與時間有關的錯誤也不會再產生了。
但,應注意:同一時刻,只能有一個執行緒持有該鎖。
當A執行緒對某個全域性變數加鎖存取,B在存取前嘗試加鎖,拿不到鎖,B阻塞。C執行緒不去加鎖,而直接存取該全域性變數,依然能夠存取,但會出現數據混亂。
所以,互斥鎖實質上是操作系統提供的一把「建議鎖」(又稱「協同鎖」),建議程式中有多執行緒存取共用資源的時候使用該機制 機製。但,並沒有強制限定。
因此,即使有了mutex,如果有執行緒不按規則來存取數據,依然會造成數據混亂。
與互斥量類似,但讀寫鎖允許更高的並行性。其特性爲:寫獨佔,讀共用。
讀寫鎖也叫共用-獨佔鎖。當讀寫鎖以讀模式鎖住時,它是以共用模式鎖住的;當它以寫模式鎖住時,它是以獨佔模式鎖住的。寫獨佔、讀共用。
讀寫鎖非常適合於對數據結構讀的次數遠大於寫的情況。
協程:coroutine。也叫輕量級執行緒。
協程最大的優勢在於「輕量級」。可以輕鬆建立上萬個而不會導致系統資源衰竭。而執行緒和進程通常很難超過1萬個。這也是協程別稱「輕量級執行緒」的原因。
一個執行緒中可以有任意多個協程,但某一時刻只能有一個協程在執行,多個協程分享該執行緒分配到的計算機資源。
在協程中,呼叫一個任務就像呼叫一個函數一樣,消耗的系統資源最少!但能達到進程、執行緒併發相同的效果。
在一次併發任務中,進程、執行緒、協程均可以實現。從系統資源消耗的角度出發來看,進程相當多,執行緒次之,協程最少。
Go 在語言級別支援協程,叫goroutine。Go 語言標準庫提供的所有系統呼叫操作(包括所有同步IO操作),都會出讓CPU給其他goroutine。這讓輕量級執行緒的切換管理不依賴於系統的執行緒和進程,也不需要依賴於CPU的核心數量。
Go從語言層面就支援並行。同時,併發程式的記憶體管理有時候是非常複雜的,而Go語言提供了自動垃圾回收機制 機製。
Go語言中的併發程式主要使用兩種手段來實現。goroutine和channel。