Go語言是為并發(fā)而生的語言,Go語言是為數(shù)不多的在語言層面實現(xiàn)并發(fā)的語言;也正是Go語言的并發(fā)特性,吸引了全球無數(shù)的開發(fā)者。
并發(fā)(concurrency)和并行(parallellism)
-
并發(fā)(concurrency):兩個或兩個以上的任務在一段時間內(nèi)被執(zhí)行。我們不必care這些任務在某一個時間點是否是同時執(zhí)行,可能同時執(zhí)行,也可能不是,我們只關心在一段時間內(nèi),哪怕是很短的時間(一秒或者兩秒)是否執(zhí)行解決了兩個或兩個以上任務。
-
并行(parallellism):兩個或兩個以上的任務在同一時刻被同時執(zhí)行。
并發(fā)說的是邏輯上的概念,而并行,強調(diào)的是物理運行狀態(tài)。并發(fā)“包含”并行。
(詳情請見:Rob Pike 的PPT)
Go的CSP并發(fā)模型
Go實現(xiàn)了兩種并發(fā)形式。第一種是大家普遍認知的:多線程共享內(nèi)存。其實就是Java或者C++等語言中的多線程開發(fā)。另外一種是Go語言特有的,也是Go語言推薦的:CSP(communicating sequential processes)并發(fā)模型。
CSP并發(fā)模型是在1970年左右提出的概念,屬于比較新的概念,不同于傳統(tǒng)的多線程通過共享內(nèi)存來通信,CSP講究的是“以通信的方式來共享內(nèi)存”。
請記住下面這句話:"Do not communicate by sharing memory; instead, share memory by communicating."“不要以共享內(nèi)存的方式來通信,相反,要通過通信來共享內(nèi)存?!?/span>
普通的線程并發(fā)模型,就是像Java、C++、或者Python,他們線程間通信都是通過共享內(nèi)存的方式來進行的。非常典型的方式就是,在訪問共享數(shù)據(jù)(例如數(shù)組、Map、或者某個結構體或對象)的時候,通過鎖來訪問,因此,在很多時候,衍生出一種方便操作的數(shù)據(jù)結構,叫做“線程安全的數(shù)據(jù)結構”。例如Java提供的包”java.util.concurrent”中的數(shù)據(jù)結構。Go中也實現(xiàn)了傳統(tǒng)的線程并發(fā)模型。
Go的CSP并發(fā)模型,是通過goroutine
和channel
來實現(xiàn)的。
-
goroutine
是Go語言中并發(fā)的執(zhí)行單位。有點抽象,其實就是和傳統(tǒng)概念上的”線程“類似,可以理解為”線程“。 -
channel
是Go語言中各個并發(fā)結構體(goroutine
)之前的通信機制。通俗的講,就是各個goroutine
之間通信的”管道“,有點類似于Linux中的管道。
生成一個goroutine
的方式非常的簡單:Go一下,就生成了:
gof();
通信機制channel
也很方便,傳數(shù)據(jù)用channel <- data
,取數(shù)據(jù)用<-channel
。
在通信過程中,傳數(shù)據(jù)channel <- data
和取數(shù)據(jù)<-channel
必然會成對出現(xiàn),因為這邊傳,那邊取,兩個goroutine
之間才會實現(xiàn)通信。
而且不管傳還是取,必阻塞,直到另外的goroutine
傳或者取為止。
有兩個goroutine
,其中一個發(fā)起了向channel
中發(fā)起了傳值操作。(goroutine
為矩形,channel
為箭頭)

左邊的goroutine
開始阻塞,等待有人接收。
這時候,右邊的goroutine
發(fā)起了接收操作

右邊的goroutine
也開始阻塞,等待別人傳送。
這時候,兩邊goroutine
都發(fā)現(xiàn)了對方,于是兩個goroutine
開始一傳,一收。

這便是Golang CSP并發(fā)模型最基本的形式。
Go并發(fā)模型的實現(xiàn)原理
我們先從線程講起,無論語言層面何種并發(fā)模型,到了操作系統(tǒng)層面,一定是以線程的形態(tài)存在的。而操作系統(tǒng)根據(jù)資源訪問權限的不同,體系架構可分為用戶空間和內(nèi)核空間;內(nèi)核空間主要操作訪問CPU資源、I/O資源、內(nèi)存資源等硬件資源,為上層應用程序提供最基本的基礎資源,用戶空間呢就是上層應用程序的固定活動空間,用戶空間不可以直接訪問資源,必須通過“系統(tǒng)調(diào)用”、“庫函數(shù)”或“Shell腳本”來調(diào)用內(nèi)核空間提供的資源。
我們現(xiàn)在的計算機語言,可以狹義的認為是一種“軟件”,它們中所謂的“線程”,往往是用戶態(tài)的線程,和操作系統(tǒng)本身內(nèi)核態(tài)的線程(簡稱KSE),還是有區(qū)別的。
線程模型的實現(xiàn),可以分為以下幾種方式:
用戶級線程模型
如圖所示,多個用戶態(tài)的線程對應著一個內(nèi)核線程,程序線程的創(chuàng)建、終止、切換或者同步等線程工作必須自身來完成。

內(nèi)核級線程模型
這種模型直接調(diào)用操作系統(tǒng)的內(nèi)核線程,所有線程的創(chuàng)建、終止、切換、同步等操作,都由內(nèi)核來完成。C++就是這種。

兩級線程模型
這種模型是介于用戶級線程模型和內(nèi)核級線程模型之間的一種線程模型。這種模型的實現(xiàn)非常復雜,和內(nèi)核級線程模型類似,一個進程中可以對應多個內(nèi)核級線程,但是進程中的線程不和內(nèi)核線程一一對應;這種線程模型會先創(chuàng)建多個內(nèi)核級線程,然后用自身的用戶級線程去對應創(chuàng)建的多個內(nèi)核級線程,自身的用戶級線程需要本身程序去調(diào)度,內(nèi)核級的線程交給操作系統(tǒng)內(nèi)核去調(diào)度。
Go語言的線程模型就是一種特殊的兩級線程模型。暫且叫它“MPG”模型吧。

Go線程實現(xiàn)模型MPG
M
指的是Machine
,一個M直接關聯(lián)了一個內(nèi)核線程。P
指的是”processor”,代表了M
所需的上下文環(huán)境,也是處理用戶級代碼邏輯的處理器。G
指的是Goroutine
,其實本質上也是一種輕量級的線程。
三者關系如下圖所示:

以上這個圖講的是兩個線程(內(nèi)核線程)的情況。一個M會對應一個內(nèi)核線程,一個M也會連接一個上下文P,一個上下文P相當于一個“處理器”,一個上下文連接一個或者多個Goroutine
。P(Processor)
的數(shù)量是在啟動時被設置為環(huán)境變量GOMAXPROCS
的值,或者通過運行時調(diào)用函數(shù)runtime.GOMAXPROCS()
進行設置。Processor
數(shù)量固定意味著任意時刻只有固定數(shù)量的線程在運行go代碼。Goroutine
中就是我們要執(zhí)行并發(fā)的代碼。圖中P正在執(zhí)行的Goroutine
為藍色的;處于待執(zhí)行狀態(tài)的Goroutine
為灰色的,灰色的Goroutine
形成了一個隊列runqueues
。
三者關系的宏觀的圖為:

拋棄 P(Processor)
你可能會想,為什么一定需要一個上下文,我們能不能直接除去上下文,讓Goroutine
的runqueues
掛到M
上呢?答案是不行,需要上下文的目的,是讓我們可以直接放開其他線程,當遇到內(nèi)核線程阻塞的時候。
一個很簡單的例子就是系統(tǒng)調(diào)用sysall
,一個線程肯定不能同時執(zhí)行代碼和系統(tǒng)調(diào)用被阻塞,這個時候,此線程M需要放棄當前的上下文環(huán)境P
,以便可以讓其他的Goroutine
被調(diào)度執(zhí)行。

如上圖左圖所示,M0
中的G0
執(zhí)行了syscall
,然后就創(chuàng)建了一個M1
(也有可能本身就存在,沒創(chuàng)建),(轉向右圖)然后M0
丟棄了P
,等待syscall
的返回值,M1
接受了P
,將·繼續(xù)執(zhí)行Goroutine
隊列中的其他Goroutine
。
當系統(tǒng)調(diào)用syscall
結束后,M0
會“偷”一個上下文,如果不成功,M0
就把它的Gouroutine
G0
放到一個全局的runqueue
中,然后自己放到線程池或者轉入休眠狀態(tài)。全局runqueue
是各個P
在運行完自己的本地的Goroutine runqueue
后用來拉取新goroutine
的地方。P
也會周期性的檢查這個全局runqueue
上的goroutine
,否則,全局runqueue
上的goroutines
可能得不到執(zhí)行而餓死。
均衡的分配工作
按照以上的說法,上下文P
會定期的檢查全局的goroutine
隊列中的goroutine
,以便自己在消費掉自身Goroutine
隊列的時候有事可做。假如全局goroutine
隊列中的goroutine
也沒了呢?就從其他運行的中的P
的runqueue
里偷。
每個P
中的Goroutine
不同導致他們運行的效率和時間也不同,在一個有很多P和M的環(huán)境中,不能讓一個P
跑完自身的Goroutine
就沒事可做了,因為或許其他的P有很長的goroutine
隊列要跑,得需要均衡。該如何解決呢?
Go的做法倒也直接,從其他P
中偷一半!

原文標題:Golang 并發(fā)原理分析
文章出處:【微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。
-
原理
+關注
關注
4文章
550瀏覽量
45202 -
模型
+關注
關注
1文章
3464瀏覽量
49819 -
go語言
+關注
關注
1文章
158瀏覽量
9242
原文標題:Golang 并發(fā)原理分析
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關推薦
三十分鐘入門基礎Go Java小子版

【GoRK3288】1.Rockchip RK3288, GO!GO!!GO!!!
Go語言開發(fā)有什么優(yōu)勢?怎么學?
Go開發(fā)語言的優(yōu)勢在哪里?
Lite Actor:方舟Actor并發(fā)模型的輕量級優(yōu)化
Go語言及Beego框架環(huán)境搭建相關資料推薦
移動應用高級語言開發(fā)——并發(fā)探索
負荷管理系統(tǒng)中的并發(fā)通信設計與實現(xiàn)
dubbo-go 中的 TPS Limit 設計與實現(xiàn)
詳解剖析Go語言調(diào)度模型的設計

golang并發(fā)機制和其他語言在實現(xiàn)上有什么不同

關于Actor并發(fā)模型的解析
NVIDIA Triton 系列文章(10):模型并發(fā)執(zhí)行
shell腳本實現(xiàn)并發(fā)多進程
go語言如何解決并發(fā)問題

評論