Go語(yǔ)言CSP并發(fā)模型goroutine及channel底層實(shí)現(xiàn)原理
參考Go的CSP并發(fā)模型實(shí)現(xiàn):M, P, G
Go語(yǔ)言是為并發(fā)而生的語(yǔ)言,Go語(yǔ)言是為數(shù)不多的在語(yǔ)言層面實(shí)現(xiàn)并發(fā)的語(yǔ)言。
并發(fā)(concurrency):多個(gè)任務(wù)在同一段時(shí)間內(nèi)運(yùn)行。
并行(parallellism):多個(gè)任務(wù)在同一時(shí)刻運(yùn)行。
Go的CSP并發(fā)模型(goroutine + channel)
Go實(shí)現(xiàn)了兩種并發(fā)形式。
- 多線程共享內(nèi)存:Java或者C++等語(yǔ)言中的多線程開發(fā)。
- CSP(communicating sequential processes)并發(fā)模型:Go語(yǔ)言特有且推薦使用的。
不同于傳統(tǒng)的多線程通過(guò)共享內(nèi)存來(lái)通信,CSP講究的是“以通信的方式來(lái)共享內(nèi)存”。
普通的線程并發(fā)模型,就是像Java、C++、或者Python,他們線程間通信都是通過(guò)共享內(nèi)存的方式來(lái)進(jìn)行的。非常典型的方式就是,在訪問(wèn)共享數(shù)據(jù)(例如數(shù)組、Map、或者某個(gè)結(jié)構(gòu)體或?qū)ο螅┑臅r(shí)候,通過(guò)鎖來(lái)訪問(wèn),因此,在很多時(shí)候,衍生出一種方便操作的數(shù)據(jù)結(jié)構(gòu),叫做“線程安全的數(shù)據(jù)結(jié)構(gòu)”。
Go的CSP并發(fā)模型,是通過(guò)goroutine和channel來(lái)實(shí)現(xiàn)的。
- goroutine 是Go語(yǔ)言中并發(fā)的執(zhí)行單位??梢岳斫鉃橛脩艨臻g的線程。
- channel是Go語(yǔ)言中不同goroutine之間的通信機(jī)制,即各個(gè)goroutine之間通信的”管道“,有點(diǎn)類似于Linux中的管道。
1、goroutine
Go語(yǔ)言最大的特色就是從語(yǔ)言層面支持并發(fā)(goroutine),goroutine是Go中最基本的執(zhí)行單元。事實(shí)上每一個(gè)Go程序至少有一個(gè)goroutine:主goroutine。當(dāng)程序啟動(dòng)時(shí),它會(huì)自動(dòng)創(chuàng)建。我們?cè)谑褂肎o語(yǔ)言進(jìn)行開發(fā)時(shí),一般會(huì)使用goroutine來(lái)處理并發(fā)任務(wù)。
goroutine機(jī)制有點(diǎn)像線程池:
go 內(nèi)部有三個(gè)對(duì)象: P(processor) 代表上下文(M所需要的上下文環(huán)境,也就是處理用戶級(jí)代碼邏輯的處理器),M(work thread)代表內(nèi)核線程,G(goroutine)協(xié)程。
正常情況下一個(gè)cpu核運(yùn)行一個(gè)內(nèi)核線程,一個(gè)內(nèi)核線程運(yùn)行一個(gè)goroutine協(xié)程。當(dāng)一個(gè)goroutine阻塞時(shí),會(huì)啟動(dòng)一個(gè)新的內(nèi)核線程來(lái)運(yùn)行其他goroutine,以充分利用cpu資源。所以線程往往會(huì)比cpu核數(shù)更多。
example
在單核情況下,所有g(shù)oroutine運(yùn)行在同一個(gè)內(nèi)核線程(M0)中,每一個(gè)內(nèi)核線程維護(hù)一個(gè)上下文(P),任何時(shí)刻,一個(gè)上下文中只有一個(gè)goroutine,其他goroutine在runqueue中等待。一個(gè)goroutine運(yùn)行完自己的時(shí)間片后,讓出上下文,自己回到runqueue中。如下圖左邊所示,只有一個(gè)G0在運(yùn)行,而其他goroutine都掛起了。
當(dāng)正在運(yùn)行的G0阻塞的時(shí)候(IO之類的),會(huì)再創(chuàng)建一個(gè)新的內(nèi)核線程(M1),P轉(zhuǎn)到新的內(nèi)核線程中去運(yùn)行。
當(dāng)M0返回時(shí)(不再阻塞),它會(huì)嘗試從其他線程中“偷”一個(gè)上下文(cpu)過(guò)來(lái),如果沒(méi)有偷到,會(huì)把goroutine放到global runqueue中去,然后把自己放入線程緩存中。上下文會(huì)定時(shí)檢查global runqueue切換goroutine運(yùn)行。
goroutine的優(yōu)點(diǎn):
1、創(chuàng)建與銷毀的開銷小
線程創(chuàng)建時(shí)需要向操作系統(tǒng)申請(qǐng)資源,并且在銷毀時(shí)將資源歸還,因此它的創(chuàng)建和銷毀的開銷比較大。相比之下,goroutine的創(chuàng)建和銷毀是由go語(yǔ)言在運(yùn)行時(shí)自己管理的,因此開銷更低。所以一個(gè)Golang的程序中可以支持10w級(jí)別的Goroutine。每個(gè) goroutine (協(xié)程) 默認(rèn)占用內(nèi)存遠(yuǎn)比 Java 、C 的線程少(*goroutine:*2KB ,線程:8MB)
2、切換開銷小
這是goroutine于線程的主要區(qū)別,也是golang能夠?qū)崿F(xiàn)高并發(fā)的主要原因。
線程的調(diào)度方式是搶占式的,如果一個(gè)線程的執(zhí)行時(shí)間超過(guò)了分配給它的時(shí)間片,就會(huì)被其它可執(zhí)行的線程搶占。在線程切換的過(guò)程中需要保存/恢復(fù)所有的寄存器信息,比如16個(gè)通用寄存器,PC(Program Counter),SP(Stack Pointer),段寄存器等等。
而goroutine的調(diào)度是協(xié)同式的,沒(méi)有時(shí)間片的概念,由Golang完成,它不會(huì)直接地與操作系統(tǒng)內(nèi)核打交道。當(dāng)goroutine進(jìn)行切換的時(shí)候,之后很少量的寄存器需要保存和恢復(fù)(PC和SP)。因此gouroutine的切換效率更高。
總的來(lái)說(shuō),操作系統(tǒng)的一個(gè)線程下可以并發(fā)執(zhí)行上千個(gè)goroutine,每個(gè)goroutine所占用的資源和切換開銷都很小,因此,goroutine是golang適合高并發(fā)場(chǎng)景的重要原因。
生成一個(gè)goroutine的方法十分簡(jiǎn)單,直接使用go關(guān)鍵字即可:
go func();
2、channel
參考由淺入深剖析 go channel
channel的使用方法:聲明之后,傳數(shù)據(jù)用channel <- data,取數(shù)據(jù)用<-channel。channel分為無(wú)緩沖和有緩沖,無(wú)緩沖會(huì)同步阻塞,即每次生產(chǎn)消息都會(huì)阻塞到消費(fèi)者將消息消費(fèi);有緩沖的不會(huì)立刻阻塞。
無(wú)緩存channel
ch := make(chan int) // write to channel ch <- x // read from channel x <- ch // another way to read x = <- ch
從無(wú)緩存的 channel 中讀取消息會(huì)阻塞,直到有 goroutine 向該 channel 中發(fā)送消息;同理,向無(wú)緩存的 channel 中發(fā)送消息也會(huì)阻塞,直到有 goroutine 從 channel 中讀取消息。
example
c := make(chan int) // Allocate a channel. // Start the sort in a goroutine; when it completes, signal on the channel. go func() { list.Sort() c <- 1 // Send a signal; value does not matter. }() doSomethingForAWhile() <-c
主goroutine定義一個(gè)無(wú)緩存的channel,然后開啟一個(gè)新的goroutine執(zhí)行排序任務(wù),接著主goroutine繼續(xù)向下執(zhí)行doSomethingForAWhile,接著要從channel中取值,但是channel是空的,因此主goroutine阻塞。等到新goroutine排序完畢,向channel中寫值后,主goroutine從channel中取到值,然后才能繼續(xù)向下執(zhí)行。
有緩存channel
有緩存的 channel 的聲明方式為指定 make 函數(shù)的第二個(gè)參數(shù),該參數(shù)為 channel 緩存的容量
ch := make(chan int, 10)
當(dāng)緩存未滿時(shí),向 channel 中發(fā)送消息時(shí)不會(huì)阻塞,當(dāng)緩存滿時(shí),發(fā)送操作將被阻塞,直到有其他 goroutine 從中讀取消息
ch := make(chan int, 3) // blocked, read from empty buffered channel <- ch
相應(yīng)的,當(dāng) channel 中消息不為空時(shí),讀取消息不會(huì)出現(xiàn)阻塞,當(dāng) channel 為空時(shí),讀取操作會(huì)造成阻塞,直到有 goroutine 向 channel 中寫入消息。
ch := make(chan int, 3) ch <- 1 ch <- 2 ch <- 3 // blocked, send to full buffered channel ch <- 4
通過(guò) len 函數(shù)可以獲得 chan 中的元素個(gè)數(shù),通過(guò) cap 函數(shù)可以得到 channel 的緩存長(zhǎng)度。
channel 也可以使用 range 取值,并且會(huì)一直從 channel 中讀取數(shù)據(jù),直到有 goroutine 對(duì)改 channel 執(zhí)行 close 操作,循環(huán)才會(huì)結(jié)束。
// consumer worker ch := make(chan int, 10) for x := range ch{ fmt.Println(x) }
等價(jià)于
for { x, ok := <- ch if !ok { break } fmt.Println(x) }
3、Go并發(fā)模型的底層實(shí)現(xiàn)原理
無(wú)論在語(yǔ)言層面用的是何種并發(fā)模型,到了操作系統(tǒng)層面,一定是以線程的形態(tài)存在的。而操作系統(tǒng)根據(jù)資源訪問(wèn)權(quán)限的不同,體系架構(gòu)可分為用戶空間和內(nèi)核空間。
- 內(nèi)核空間主要操作訪問(wèn)CPU資源、I/O資源、內(nèi)存資源等硬件資源,為上層應(yīng)用程序提供最基本的基礎(chǔ)資源。
- 用戶空間就是上層應(yīng)用程序的固定活動(dòng)空間,用戶空間不可以直接訪問(wèn)資源,必須通過(guò)“系統(tǒng)調(diào)用”、“庫(kù)函數(shù)”或“Shell腳本”來(lái)調(diào)用內(nèi)核空間提供的資源。
golang使用goroutine做為最小的執(zhí)行單位,但是這個(gè)執(zhí)行單位還是在用戶空間,實(shí)際上最后被處理器執(zhí)行的還是內(nèi)核中的線程,用戶線程和內(nèi)核線程的調(diào)度方法有:
- 1:1,即一個(gè)內(nèi)核線程對(duì)應(yīng)一個(gè)用戶級(jí)線程(并發(fā)度低,浪費(fèi)cpu資源,上下文切換需要消耗額外的資源)。
- 1:N,即一個(gè)內(nèi)核線程對(duì)應(yīng)N個(gè)用戶級(jí)線程(并發(fā)度高,但是只用一個(gè)內(nèi)核線程,不能有效利用多核CPU)。
- M:N,即M個(gè)內(nèi)核線程對(duì)應(yīng)N個(gè)用戶級(jí)線程(上述兩種方式的折中,缺點(diǎn)是線程調(diào)度會(huì)復(fù)雜一些)
golang 通過(guò)為goroutine提供語(yǔ)言層面的調(diào)度器,來(lái)實(shí)現(xiàn)了高效率的M:N線程對(duì)應(yīng)關(guān)系
M:是內(nèi)核線程
P : 是調(diào)度協(xié)調(diào),用于協(xié)調(diào)M和G的執(zhí)行,內(nèi)核線程只有拿到了 P才能對(duì)goroutine繼續(xù)調(diào)度執(zhí)行,一般都是通過(guò)限定P的個(gè)數(shù)來(lái)控制golang的并發(fā)度
G : 是待執(zhí)行的goroutine,包含這個(gè)goroutine的棧空間
Gn : 灰色背景的Gn 是已經(jīng)掛起的goroutine,它們被添加到了執(zhí)行隊(duì)列中,然后需要等待網(wǎng)絡(luò)IO的goroutine,當(dāng)P通過(guò) epoll查詢到特定的fd的時(shí)候,會(huì)重新調(diào)度起對(duì)應(yīng)的,正在掛起的goroutine。
Golang為了調(diào)度的公平性,在調(diào)度器加入了steal working 算法 ,在一個(gè)P自己的執(zhí)行隊(duì)列,處理完之后,它會(huì)先到全局的執(zhí)行隊(duì)列中偷G進(jìn)行處理,如果沒(méi)有的話,再會(huì)到其他P的執(zhí)行隊(duì)列中搶G來(lái)進(jìn)行處理。
4、一個(gè)CSP例子
生產(chǎn)者-消費(fèi)者Sample:
package main import ( "fmt" "time" ) // 生產(chǎn)者 func Producer (queue chan<- int){ for i:= 0; i < 10; i++ { queue <- i } } // 消費(fèi)者 func Consumer( queue <-chan int){ for i :=0; i < 10; i++{ v := <- queue fmt.Println("receive:", v) } } func main(){ queue := make(chan int, 1) go Producer(queue) go Consumer(queue) time.Sleep(1e9) //讓Producer與Consumer完成 }
生產(chǎn)者goroutine往channel傳值,消費(fèi)者goroutine往channel取值,這兩個(gè)goroutine通過(guò)channel完成通信。
以上就是Go語(yǔ)言CSP并發(fā)模型goroutine channel底層實(shí)現(xiàn)原理的詳細(xì)內(nèi)容,更多關(guān)于go CSP并發(fā)模型goroutine channel的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用Go語(yǔ)言開發(fā)一個(gè)命令行文件管理工具
這篇文章主要為大家詳細(xì)介紹了如何使用Go語(yǔ)言開發(fā)一款命令行文件管理工具,支持批量重命名,刪除,創(chuàng)建,移動(dòng)文件,需要的小伙伴可以了解下2025-02-02重學(xué)Go語(yǔ)言之錯(cuò)誤處理與異常機(jī)制詳解
Go語(yǔ)言的開發(fā)者顯然覺得?try-catch被濫用了,因此?Go不支持使用?try-catch語(yǔ)句捕獲異常處理,那么,Go語(yǔ)言是如何定義和處理程序的異常呢,下面我們就來(lái)看看吧2023-08-08go語(yǔ)言使用Casbin實(shí)現(xiàn)角色的權(quán)限控制
Casbin是用于Golang項(xiàng)目的功能強(qiáng)大且高效的開源訪問(wèn)控制庫(kù)。本文主要介紹了go語(yǔ)言使用Casbin實(shí)現(xiàn)角色的權(quán)限控制,感興趣的可以了解下2021-06-06Golang內(nèi)存泄漏詳解之原因、檢測(cè)與修復(fù)過(guò)程
本文詳細(xì)介紹了Golang中的內(nèi)存泄漏問(wèn)題,包括內(nèi)存泄漏的定義、分類、影響以及預(yù)防和修復(fù)方法,通過(guò)使用Golang自帶的性能分析工具和火焰圖工具,可以有效地檢測(cè)和定位內(nèi)存泄漏的代碼路徑,合理的代碼設(shè)計(jì)和定期的代碼審查也是預(yù)防內(nèi)存泄漏的關(guān)鍵2024-12-12一文帶你了解Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)strings的常用函數(shù)和方法
strings?庫(kù)包含了許多高效的字符串常用操作的函數(shù)和方法,巧用這些函數(shù)與方法,能極大的提高我們程序的性能。本文就來(lái)和大家分享一下Go標(biāo)準(zhǔn)庫(kù)strings的常用函數(shù)和方法,希望對(duì)大家有所幫助2022-11-11詳解golang函數(shù)多返回值錯(cuò)誤處理與error類型
這篇文章主要為大家詳細(xì)介紹了golang中函數(shù)多返回值錯(cuò)誤處理與error類型的相關(guān)知識(shí),文中的示例代碼簡(jiǎn)潔易懂,感興趣的小伙伴快跟隨小編一起學(xué)習(xí)吧2023-10-10golang與非golang程序探測(cè)beyla源碼解讀
這篇文章主要為大家介紹了beyla源碼解讀之golang與非golang程序的探測(cè)實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12GO語(yǔ)言gin框架實(shí)現(xiàn)管理員認(rèn)證登陸接口
這篇文章主要介紹了GO語(yǔ)言gin框架實(shí)現(xiàn)管理員認(rèn)證登陸接口,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10