Go語言如何通過通信共享內(nèi)存
介紹
Go 語言使用 goroutine 和 channel,可以實現(xiàn)通過通信共享內(nèi)存。
本文我們介紹 Go 語言怎么通過通信共享內(nèi)存。
goroutine 和 channel
在了解 Go 語言怎么通過通信共享內(nèi)存之前。我們需要先了解一些預(yù)備知識,即 goroutine 和 channel 是什么?
goroutine :
goroutine 具有簡單的模型:它是與其它 goroutine 并發(fā)運(yùn)行在同一地址空間的函數(shù)。
goroutine 是輕量級的,所有消耗幾乎就只有??臻g的分配。而且棧最開始是非常小的,所以他們很廉價,僅在需要時才會隨著堆空間的分配(和釋放)而變化。
摘自「Effective Go - channels[1]」。
注意:goroutine 之所以取名為 goroutine,是因為現(xiàn)有的術(shù)語 - 線程、協(xié)程、進(jìn)程等等 - 無法準(zhǔn)確傳達(dá)它的含義。也有些資料將 goroutine 翻譯為 Go 協(xié)程或 Go 程。
使用 goroutine 也非常簡單,在函數(shù)或方法前添加 go 關(guān)鍵字,即可在新的 goroutine 中調(diào)用它。當(dāng)調(diào)用完成后,該 goroutine 也會安靜地退出。
此外,匿名函數(shù)也可以在 goroutine 中調(diào)用。
關(guān)于 goroutine 的實現(xiàn)原理和調(diào)度器模型 GPM,感興趣的讀者朋友們可以自行查閱相關(guān)資料。
channel:
我們已了解,什么是 goroutine,以及怎么使用 goroutine 調(diào)用函數(shù)或方法、匿名函數(shù)。
但是,想要實現(xiàn) goroutine 之間的通信,我們還需要了解 channel。
channel 需要使用內(nèi)置函數(shù) make 分配內(nèi)存,其結(jié)果值充當(dāng)了對底層數(shù)據(jù)結(jié)構(gòu)的引用。如果提供了一個可選的參數(shù),它就會為該 channel 設(shè)置緩沖區(qū)大小,否則,該 channel 則為無緩沖區(qū)的 channel。
關(guān)于 channel 的實現(xiàn)原理,感興趣的讀者朋友們可以閱讀「Golang 語言中的 channel 實現(xiàn)原理」。
需要注意的是,兩個 goroutine 之間通過無緩沖區(qū)的 channel 通信時,同步交換數(shù)據(jù)。
作為兩個 goroutine 之間的通信管道,向 channel 中發(fā)送數(shù)據(jù)的 goroutine 稱為“發(fā)送者”,反之,從 channel 中接收數(shù)據(jù)的 goroutine 稱為“接收者”。
通過通信共享內(nèi)存
我們已經(jīng)基本了解 Go 語言的 goroutine 和 channel,接下來我們看一下兩個 goroutine 之間怎么使用 channel (無緩沖區(qū)和緩沖區(qū))進(jìn)行通信?
無緩沖區(qū) channel:
示例代碼:
func main() {
c := make(chan int) // 定義一個無緩沖區(qū) channel
go func() { // 啟動一個 goroutine 調(diào)用匿名函數(shù)
fmt.Println("啟動一個 goroutine 調(diào)用匿名函數(shù)")
c <- 1 // 該 goroutine 向 channel 發(fā)送一個值(信號)
}()
fmt.Println("main 函數(shù)")
out := <-c // main goroutine 從 channel 中接收一個值(信號),再未接收到值(信號)之前,一直阻塞
fmt.Println(out) // 該打印無實際意義,僅為了讀者容易理解
}
閱讀上面這段代碼,我們定義一個無緩沖區(qū) channel,執(zhí)行匿名函數(shù)的 goroutine 作為發(fā)送者,main goroutine 作為接收者。
需要注意的是,無緩沖區(qū) channel,接收者在收到值之前,發(fā)送者會一直阻塞。同理,發(fā)送者在發(fā)送值之前,接收者也會一直阻塞。
緩沖區(qū) channel:
示例代碼:
func main() {
// c := make(chan int) // 無緩沖區(qū) channel
c := make(chan int, 5) // 緩沖區(qū) channel
for i := 0; i < 20; i++ {
c <- 1
go func() {
fmt.Println("do something:", i)
<-c
}()
}
time.Sleep(time.Second * 2) // 為了防止 main goroutine 提前退出
}
閱讀上面這段代碼,我們定義一個緩沖區(qū)大小為 5 的 channel,執(zhí)行匿名函數(shù)的 goroutine 作為接收者,main goroutine 作為發(fā)送者。
需要注意的是,該段代碼中有 5 個執(zhí)行匿名函數(shù)的 goroutine,即 N 個接收者,1 個發(fā)送者(main goroutine)。
我們前面講過,接收者在收到值之前會一直阻塞,而無緩沖區(qū) channel 在接收者收到值之前,發(fā)送者會一直阻塞。
如果我們將上面這段代碼中的緩沖區(qū) channel 換成無緩沖區(qū) channel,N - 1 個接收者在接收到值之前,發(fā)送者會一直阻塞,發(fā)送者阻塞,導(dǎo)致接收者一直接收不到值,也會一直阻塞,從而導(dǎo)致死鎖。
上面這段話有些拗口,讀者朋友們可以通過運(yùn)行使用無緩沖區(qū) channel 的代碼來幫助自己理解。
我們運(yùn)行使用緩沖區(qū)大小為 5 的 channel 的代碼,發(fā)現(xiàn)代碼可以正常運(yùn)行,發(fā)送者和接收者之間不會產(chǎn)生死鎖。
這是因為緩沖區(qū) channel,發(fā)送者僅在值被復(fù)制到緩沖區(qū)之前阻塞,如果緩沖區(qū)已滿,發(fā)送者會一直阻塞,直到某個接收者取出一個值。
回到上面這段示例代碼中,執(zhí)行匿名函數(shù)的 N 個 goroutine 作為接收者,在沒有收到 main goroutine 發(fā)送的數(shù)據(jù)之前,一直處于阻塞狀態(tài),直到作為發(fā)送者的 main goroutine 發(fā)送數(shù)據(jù)到緩沖區(qū) channel 中。
讀者朋友們?nèi)绻屑?xì)閱讀這段代碼,會發(fā)現(xiàn)上面這段代碼雖然不會產(chǎn)生死鎖,但是存在一個 bug。
解決方案可以閱讀我們之前的一篇文章「Go 語言使用 goroutine 運(yùn)行閉包的“坑”」,限于篇幅,我就不在本文中贅述了。
總結(jié)
本文我們介紹 Go 語言中,什么是 goroutine 和 channel,其中 channel 分為無緩沖區(qū)和緩沖區(qū)。
在簡單了解 goroutine 和 channel 后,我們又介紹怎么使用 channel,實現(xiàn)兩個 goroutine 之間通信。
以上就是Go語言如何通過通信共享內(nèi)存的詳細(xì)內(nèi)容,更多關(guān)于Go語言通信共享內(nèi)存的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
golang協(xié)程關(guān)閉踩坑實戰(zhàn)記錄
協(xié)程(coroutine)是Go語言中的輕量級線程實現(xiàn),下面這篇文章主要給大家介紹了關(guān)于golang協(xié)程關(guān)閉踩坑的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-03-03
基于Golang+Vue編寫一個手機(jī)遠(yuǎn)程控制電腦的懶人工具
這篇文章主要為大家詳細(xì)介紹了如何基于Golang+Vue編寫一個手機(jī)遠(yuǎn)程控制電腦的懶人工具,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下2024-11-11
Go實現(xiàn)簡單的數(shù)據(jù)庫表轉(zhuǎn)結(jié)構(gòu)體詳解
這篇文章主要為大家介紹了Go實現(xiàn)簡單的數(shù)據(jù)庫表轉(zhuǎn)結(jié)構(gòu)體詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
Go語言使用sort包對任意類型元素的集合進(jìn)行排序的方法
這篇文章主要介紹了Go語言使用sort包對任意類型元素的集合進(jìn)行排序的方法,實例分析了sort排序所涉及的方法與相關(guān)的使用技巧,需要的朋友可以參考下2015-02-02

