Golang控制通道實(shí)現(xiàn)協(xié)程等待詳解
前言
上一次簡(jiǎn)單了解了協(xié)程的工作原理 前文鏈接
最后提到了幾個(gè)使用協(xié)程時(shí)會(huì)遇到的問題,其中一個(gè)就是主線程不會(huì)等待子線程結(jié)束,在這里記錄兩種比較簡(jiǎn)單的方法,并借此熟悉下通道的概念。
方法一-睡眠等待
簡(jiǎn)單暴力的解決方案,在創(chuàng)建了子協(xié)程之后,主協(xié)程等待一段時(shí)間再結(jié)束。
func goroutineTest(i int) { fmt.Println(i) } func main() { for i := 0; i < 3; i++ { go func() { fmt.Println(i) }() } //等待一段時(shí)間 time.Sleep(time.Millisecond * 500) }
簡(jiǎn)單暴力但是并不實(shí)用,缺點(diǎn)十分明顯, 我們并不知道子協(xié)程什么時(shí)候會(huì)全部執(zhí)行結(jié)束,要么等待時(shí)間太短,要么等待時(shí)間太長影響方法執(zhí)行性能。
所以我們需要其他更靈活的方式。
方法二-通道
什么是通道
通道是 Go 自帶的、唯一的可以滿足并發(fā)安全的類型,通道相當(dāng)于是一個(gè)先進(jìn)先出的隊(duì)列,通道中的元素會(huì)按照插入進(jìn)來時(shí)候的順序再發(fā)送出去。
通道的特性
一、進(jìn)出通道的值都是副本數(shù)據(jù)
當(dāng)向通道內(nèi)傳值時(shí),傳遞的其實(shí)是原本元素的副本,移動(dòng)時(shí)也是同理。
二、對(duì)于同一個(gè)通道,發(fā)送操作之間是互斥的,接收操作之間也是互斥的。
當(dāng)有操作要向通道發(fā)送數(shù)據(jù)的時(shí)候,其他操作的發(fā)送處理會(huì)被阻塞,只有當(dāng)前面的操作執(zhí)行完畢,值完全被復(fù)制進(jìn)通道內(nèi)之后,后面的操作才可以發(fā)送。接收通道里的值也是同理,只有當(dāng)值被接受,且元素在通道內(nèi)被刪除之后,其他的接受操作才能被執(zhí)行。
三、對(duì)于同一個(gè)通道同一元素值來說,發(fā)送和接受操作也是互斥的。
因?yàn)樯厦嫣岬皆剡M(jìn)出通道是通過副本的方式,這樣做可以避免出現(xiàn)復(fù)制還未完成就有操作將其取走的情況。
四、發(fā)送操作和接收操作中對(duì)元素值的處理是絕對(duì)完整的。
發(fā)送至通道的操作時(shí),絕不會(huì)出現(xiàn)只復(fù)制一部分的情況,同理接收通道時(shí),通道在準(zhǔn)備好元素值的副本之后一定會(huì)將通道內(nèi)的原值刪除。
五、發(fā)送和接受操作在未完成之前會(huì)一直阻塞
其實(shí)就是為了實(shí)現(xiàn)操作的互斥和元素值的完整。
什么是非緩沖通道
無緩沖隊(duì)列表示長度為 0 的通道,長度為 0 表示這個(gè)通道不能保留信息,數(shù)據(jù)是直接從發(fā)送方復(fù)制到接收方,當(dāng)有信息傳進(jìn)來時(shí),發(fā)送方會(huì)阻塞,直到有接收方接受這個(gè)值,當(dāng)然我們也可以先找到一個(gè)接收方去接收(但是這里有個(gè)坑>>>需要注意死鎖)。
什么是緩沖通道
緩沖隊(duì)列就是表示長度大于 0 的通道,這里的通道相當(dāng)于一個(gè)中轉(zhuǎn)倉庫。如果通道滿了,那么對(duì)它的所有發(fā)送操作都會(huì)被阻塞,直到通道中有元素值被接收走,通道會(huì)優(yōu)先通知最早因此在等待的發(fā)送操作。同理,如果通道已空,那么對(duì)它的所有接收操作都會(huì)被阻塞,直到通道中有新的元素值出現(xiàn)。這時(shí),通道會(huì)通知最早等待的那個(gè)接收操作。
通道的簡(jiǎn)單使用
- 在聲明通道時(shí),我們要定義通道內(nèi)元素的類型 chan [type]
- <- 是發(fā)送至通道和接收通道元素的操作符,通道在左側(cè)表示向通道內(nèi)發(fā)送,通道在右側(cè)表示從通道內(nèi)接收
- 通道定義之后,一定要初始化,對(duì)于沒有進(jìn)行初始化的通道,發(fā)送和接收操作都是阻塞的
非緩沖通道
定義一個(gè)非緩沖通道,通道內(nèi)元素類型為 int var ch0 = make(chan int) func set() { for i := 0; i < 3; i++ { time.Sleep(time.Millisecond * 1000) fmt.Println(i) } ch0 <- 999 } func main() { //val := <-ch0 go set() fmt.Println("work in here") val2 := <-ch0 fmt.Println("get value") fmt.Println(val2) fmt.Println("work finished") }
這段代碼主協(xié)程中先開啟了一個(gè)子協(xié)程,然后從通道中接受一個(gè)值,并打印出來,運(yùn)行結(jié)果如下。
主協(xié)程執(zhí)行到 val2 := <-ch0 這一行時(shí)發(fā)生了阻塞,三秒過后子協(xié)程向通道中發(fā)送了元素值。主協(xié)程從通道中接收到了值,然后繼續(xù)向下走。
work in here
0
1
2
get value
999
work finished
緩沖通道
func main() { 定義一個(gè)大小為?。场〉耐ǖ? chn := make(chan int, 3) chn <- 1 chn <- 2 chn <- 3 fmt.Printf("get value: %v\n", <-chn) fmt.Printf("get value: %v\n", <-chn) fmt.Printf("get value: %v\n", <-chn) }
get value: 1
get value: 2
get value: 3
小心死鎖
有一個(gè)需要注意的地方,如果我們把非緩沖通道示例代碼中主協(xié)程方法中的第一行代碼解開注釋再運(yùn)行,會(huì)發(fā)現(xiàn)程序出現(xiàn)了報(bào)錯(cuò)。
表示所有的協(xié)程都睡眠了,發(fā)生了死鎖。
fatal error: all goroutines are asleep - deadlock!
其實(shí)原因也很簡(jiǎn)單,就比如那上面的代碼來說。我們上來就在主協(xié)程中接收通道中的值,而此時(shí)通道中沒有值,所以主協(xié)程會(huì)阻塞,然而此時(shí)我們并沒有開啟其他的協(xié)程,那就相當(dāng)于這個(gè)程序后面將不會(huì)有沒有任何操作,永遠(yuǎn)鎖在了這個(gè)位置。所以如果在阻塞期間發(fā)現(xiàn)沒有正在執(zhí)行的協(xié)程,程序?qū)⒈霎惓M顺觥?/p>
所以我們?cè)趯?duì)通道進(jìn)行操作時(shí),要注意千萬不要在邏輯上把自己鎖死了。
使用通道實(shí)現(xiàn)協(xié)程等待
用起來有點(diǎn)像 JAVA 中的 CountDownLatch
- 首先我們需要知道子協(xié)程的數(shù)量 num
- 然后我們創(chuàng)建一個(gè)大小為 num 的通道
- 當(dāng)子協(xié)程完全執(zhí)行完之后就向通道中放一個(gè)元素值
- 主協(xié)程從通道中循環(huán)取值,取值的次數(shù)就是 num,取不到值時(shí)會(huì)阻塞,而這 num 個(gè)值只有當(dāng)全部子協(xié)程都執(zhí)行完畢之后才能提供全,所以這樣就實(shí)現(xiàn)了主協(xié)程等待子協(xié)程
至于為什么要?jiǎng)?chuàng)建 struct{} 類型的通道,是因?yàn)榭战Y(jié)構(gòu)體占用了0字節(jié)的內(nèi)存空間
func main() { num := 5 sign := make(chan struct{}, num) for i := 0; i < num; i++ { go func() { fmt.Println(i) sign <- struct{}{} }() } for j := 0; j < num; j++ { <-sign } }
到此這篇關(guān)于Golang控制通道實(shí)現(xiàn)協(xié)程等待詳解的文章就介紹到這了,更多相關(guān)Go協(xié)程等待內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go中RPC遠(yuǎn)程過程調(diào)用的實(shí)現(xiàn)
本文主要介紹了Go中RPC遠(yuǎn)程過程調(diào)用的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07golang 設(shè)置web請(qǐng)求狀態(tài)碼操作
這篇文章主要介紹了golang 設(shè)置web請(qǐng)求狀態(tài)碼操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12Golang構(gòu)建WebSocket服務(wù)器和客戶端的示例詳解
這篇文章主要為大家詳細(xì)介紹了如何使用Go語言構(gòu)建WebSocket服務(wù)器和客戶端,以實(shí)現(xiàn)雙向通信,文中的示例代碼講解詳細(xì),需要的小伙伴可以參考一下2023-11-11go-zero使用goctl生成mongodb的操作使用方法
mongodb是一種高性能、開源、文檔型的nosql數(shù)據(jù)庫,被廣泛應(yīng)用于web應(yīng)用、大數(shù)據(jù)以及云計(jì)算領(lǐng)域,goctl model 為 goctl 提供的數(shù)據(jù)庫模型代碼生成指令,目前支持 MySQL、PostgreSQL、Mongo 的代碼生成,本文給大家介紹了go-zero使用goctl生成mongodb的操作使用方法2024-06-06使用go實(shí)現(xiàn)常見的數(shù)據(jù)結(jié)構(gòu)
這篇文章主要介紹了使用go實(shí)現(xiàn)常見的數(shù)據(jù)結(jié)構(gòu),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03