【go語言學習】并發(fā)概念

一、并發(fā)性Concurrency

1、多任務

多任務是操作系統(tǒng)可以同時執(zhí)行多個任務。如,可以一邊聽音樂,一邊刷微博,一邊聊QQ,還能同時開微信。這就是多任務同時運行。

2、線程process與進程thread、協(xié)程coroutine

進程是一個程序在一個數據集中的一次動態(tài)執(zhí)行過程,可以簡單理解為“正在執(zhí)行的程序”,它是CPU資源分配和調度的獨立單位。

線程是操作系統(tǒng)能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發(fā)多個線程,每條線程并行執(zhí)行不同的任務。

簡單來說:

進程:指在系統(tǒng)中正在運行的一個應用程序;程序一旦運行就是進程;進程——資源分配的最小單位。
線程:系統(tǒng)分配處理器時間資源的基本單元,或者說進程之內獨立執(zhí)行的一個單元執(zhí)行流。線程——程序執(zhí)行的最小單位。

進程的局限是創(chuàng)建、撤銷和切換的開銷比較大。
線程的優(yōu)點是減小了程序并發(fā)執(zhí)行時的開銷,提高了操作系統(tǒng)的并發(fā)性能,缺點是線程沒有自己的系統(tǒng)資源,只擁有在運行時必不可少的資源,但同一進程的各線程可以共享進程所擁有的系統(tǒng)資源。

操作系統(tǒng)的設計,因此可以歸結為三點:

  • (1)以多進程形式,允許多個任務同時運行;
  • (2)以多線程形式,允許單個任務分成不同的部分運行;
  • (3)提供協(xié)調機制,一方面防止進程之間和線程之間產生沖突,另一方面允許進程之間和線程之間共享資源。

協(xié)程coroutine是一種用戶態(tài)的輕量級線程,又稱微線程,協(xié)程的調度完全由用戶控制。人們通常將協(xié)程和子程序(函數)比較著理解。 子程序調用總是一個入口,一次返回,一旦退出即完成了子程序的執(zhí)行。

與傳統(tǒng)的系統(tǒng)級線程和進程相比,協(xié)程的最大優(yōu)勢在于其"輕量級",可以輕松創(chuàng)建上百萬個而不會導致系統(tǒng)資源衰竭,而線程和進程通常最多也不能超過1萬的。這也是協(xié)程也叫輕量級線程的原因。

Go語言對于并發(fā)的實現是靠協(xié)程,coroutine

3、并行與并發(fā)

并行與并發(fā)(Concurrency and Parallelism)是兩個不同的概念,理解它們對于理解多線程模型非常重要。
在描述程序的并發(fā)或者并行時,應該說明從進程或者線程的角度出發(fā)。

  • 并發(fā):一個時間段內有很多的線程或進程在執(zhí)行,但任何時間點上都只有一個在執(zhí)行,多個線程或進程爭搶時間片輪流執(zhí)行
  • 并行:一個時間段和時間點上都有多個線程或進程在執(zhí)行

并行需要硬件支持,單核處理器只能是并發(fā),多核處理器才能做到并行執(zhí)行。

  • 并發(fā)是并行的必要條件,如果一個程序本身就不是并發(fā)的,也就是只有一個邏輯執(zhí)行順序,那么我們不可能讓其被并行處理。
  • 并發(fā)不是并行的充分條件,一個并發(fā)的程序,如果只被一個CPU進行處理(通過分時),那么它就不是并行的。

舉例:
如果我們在瀏覽web頁面時,下載文件和呈現頁面是周期交替執(zhí)行的,這就是并發(fā)。
如果是運行在多核CPU上,下載文件和呈現頁面可能同時在不同的內核中運行。這就是所謂的并行性。


并行性Parallelism不會總是導致更快的執(zhí)行時間,因為并行運行的組件可能需要相互通信,這種通信開銷很高,因此,并行程序并不總是導致更快的執(zhí)行時間!

二、幾種不同的多線程模型

線程的實現可以分為兩類:

  • 用戶級線程(User-LevelThread, ULT):用戶線程由用戶代碼支持。
  • 內核級線程(Kemel-LevelThread, KLT):內核線程由操作系統(tǒng)內核支持。

多線程模型:
多線程模型即用戶級線程和內核級線程的不同連接方式。

1、【用戶級線程模型】多對一模型(M : 1)

將多個用戶級線程映射到一個內核級線程,線程管理在用戶空間完成。 此模式中,用戶級線程對操作系統(tǒng)不可見(即透明)。

優(yōu)點: 這種模型的好處是線程上下文切換都發(fā)生在用戶空間,避免的模態(tài)切換(mode switch),從而對于性能有積極的影響。

缺點:所有的線程基于一個內核調度實體即內核線程,這意味著只有一個處理器可以被利用,在多處理器環(huán)境下這是不能夠被接受的,本質上,用戶線程只解決了并發(fā)問題,但是沒有解決并行問題。如果線程因為 I/O 操作陷入了內核態(tài),內核態(tài)線程阻塞等待 I/O 數據,則所有的線程都將會被阻塞,用戶空間也可以使用非阻塞而 I/O,但是不能避免性能及復雜度問題。

2、【內核級線程模型】一對一模型(1 : 1)

將每個用戶級線程映射到一個內核級線程。


每個線程由內核調度器獨立的調度,所以如果一個線程阻塞則不影響其他的線程。

優(yōu)點:在多核處理器的硬件的支持下,內核空間線程模型支持了真正的并行,當一個線程被阻塞后,允許另一個線程繼續(xù)執(zhí)行,所以并發(fā)能力較強。

缺點:每創(chuàng)建一個用戶級線程都需要創(chuàng)建一個內核級線程與其對應,這樣創(chuàng)建線程的開銷比較大,會影響到應用程序的性能。

3、【兩級線程模型】多對多模型(M : N)

內核線程和用戶線程的數量比為 M : N,內核用戶空間綜合了前兩種的優(yōu)點。

這種模型需要內核線程調度器和用戶空間線程調度器相互操作,本質上是多個線程被綁定到了多個內核線程上,這使得大部分的線程上下文切換都發(fā)生在用戶空間,而多個內核線程又可以充分利用處理器資源。

三、goroutine機制的調度實現

goroutine機制實現了M : N的線程模型,goroutine機制是協(xié)程(coroutine)的一種實現,golang內置的調度器,可以讓多核CPU中每個CPU執(zhí)行一個協(xié)程。

理解goroutine機制的原理,關鍵是理解Go語言調度器scheduler的實現。

1、調度器是如何工作的

Go語言中支撐整個scheduler實現的主要有4個重要結構,分別是M、G、P、Sched, 前三個定義在runtime.h中,Sched定義在proc.c中。

Sched是調度器,它維護有存儲M和G的隊列以及調度器的一些狀態(tài)信息等。

M是Machine,系統(tǒng)線程,它由操作系統(tǒng)管理的,goroutine就是跑在M之上的;M是一個很大的結構,里面維護小對象內存cache(mcache)、當前執(zhí)行的goroutine、隨機數發(fā)生器等等非常多的信息。M的作用就是執(zhí)行G中包裝的并發(fā)任務。Go運行時系統(tǒng)中的調度器的主要職責就是將G公平合理的安排到多個M上去執(zhí)行。

P是Processor,邏輯處理器,它的主要用途就是用來執(zhí)行goroutine的,它維護了一個goroutine隊列,即runqueue,并為G在M上的運行提供本地化資源。。Processor是讓我們從N:1調度到M:N調度的重要部分。

G是goroutine,用go關鍵字加函數調用的代碼就是創(chuàng)建了一個G對象,是對一個要并發(fā)執(zhí)行的任務的封裝,也可以稱作用戶態(tài)線程。屬于用戶級資源,對OS透明,具備輕量級,可以大量創(chuàng)建,上下文切換成本低等特點。

Processor的數量是在啟動時被設置為環(huán)境變量GOMAXPROCS的值,或者通過運行時調用函數GOMAXPROCS()進行設置。Processor數量固定意味著任意時刻只有GOMAXPROCS個線程在運行go代碼。

我們分別用三角形,矩形和圓形表示Machine Processor和Goroutine。


在單核處理器的場景下,所有goroutine運行在同一個M系統(tǒng)線程中,每一個M系統(tǒng)線程維護一個Processor,任何時刻,一個Processor中只有一個goroutine,其他goroutine在runqueue中等待。一個goroutine運行完自己的時間片后,讓出上下文,回到runqueue中。 多核處理器的場景下,為了運行goroutines,每個M系統(tǒng)線程會持有一個Processor。

在正常情況下,scheduler會按照上面的流程進行調度,但是線程會發(fā)生阻塞等情況,看一下goroutine對線程阻塞等的處理。

2、線程阻塞

當正在運行的goroutine阻塞的時候,例如進行系統(tǒng)調用,會再創(chuàng)建一個系統(tǒng)線程(M1),當前的M線程放棄了它的Processor,P轉到新的線程中去運行。

3、runqueue執(zhí)行完成

當其中一個Processor的runqueue為空,沒有goroutine可以調度。它會從另外一個上下文偷取一半的goroutine。

Go運行時系統(tǒng)通過構造G-P-M對象模型實現了一套用戶態(tài)的并發(fā)調度系統(tǒng),可以自己管理和調度自己的并發(fā)任務,所以可以說Go語言原生支持并發(fā)。自己實現的調度器負責將并發(fā)任務分配到不同的內核線程上運行,然后內核調度器接管內核線程在CPU上的執(zhí)行與調度。

可以看到Go的并發(fā)用起來非常簡單,用了一個語法糖將內部復雜的實現結結實實的包裝了起來。其內部可以用下面這張圖來概述:

參考文章:
https://zhuanlan.zhihu.com/p/77206570
https://www.cnblogs.com/williamjie/p/9456764.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容