詳解go語言判斷管道是否關(guān)閉的常見誤區(qū)
前言
本文是探討的是"在Go語言中,我們是否可以使用讀取管道時的第二個返回值來判斷管道是否關(guān)閉?"
樣例
在Go語言中,我們是否可以使用讀取管道時的第二個返回值來判斷管道是否關(guān)閉? 可以看下面的代碼
package main import "fmt" func main() { // 創(chuàng)建一個整型管道 ch := make(chan int) // 啟動一個協(xié)程往管道發(fā)送數(shù)據(jù) go func() { for i := 0; i < 5; i++ { ch <- i } // 關(guān)閉管道 close(ch) }() // 能否判斷管道是否關(guān)閉? if _, ok := <-ch; !ok { fmt.Println("管道已關(guān)閉") } }
探討 管道的數(shù)據(jù)結(jié)構(gòu)
在探討這個問題之前,我們先來了解一下管道的數(shù)據(jù)結(jié)構(gòu),從go的源碼,我們可以知道,管道是被定義為一個名為hchan的結(jié)構(gòu)體:
type hchan struct { qcount uint //當前隊列中剩余的元素個數(shù) dataqsiz uint // 環(huán)形隊列管道容積 buf unsafe.Pointer // 環(huán)形隊列指針 elemsize uint16 // 元素大小 closed uint32 // 標識管道關(guān)閉的狀態(tài) elemtype *_type // 元素類型 recvq waitq // 等待的讀元素的協(xié)程隊列 sendq waitq // 等待的寫元素的協(xié)程隊列 ... }
其中,有一個屬性是我們應該關(guān)注的,那就是closed,這玩意標識了管道是否關(guān)閉,這玩意為1代表關(guān)閉了,為0代表是開啟的.
詳細分析
好的,接下來我們繼續(xù)本文探討的問題在Go語言中,我們是否可以使用管道的第二個返回值來判斷管道是否關(guān)閉?先給出結(jié)論 : 從嚴格意義上來講是不可以的,其實表示是否成功讀取數(shù)據(jù),但是在緩存區(qū)為0的時候,ok的狀態(tài)和管道狀態(tài)是一致的,所以會被誤認為,這個ok是代表管道的狀態(tài)可以看下面的例子
package main import ( "fmt" "time" ) func main() { a2 := make(chan int, 2) go demo(a2) value2, ok2 := <-a2 fmt.Printf("value2:%v,ok2:%v\n", value2, ok2) time.Sleep(3 * time.Second) value3, ok3 := <-a2 fmt.Printf("value3:%v,ok3:%v\n", value3, ok3) } func demo(a chan int) { defer func() { close(a) fmt.Println("管道已經(jīng)關(guān)閉") }() a <- 1 a <- 2 }
解釋一下運行流程
1.首先創(chuàng)建了一個緩存區(qū)為2的管道a2
2.然后用go關(guān)鍵字
將demo函數(shù)開辟出一個新的協(xié)程運行,此時demo和main是同一級的關(guān)系,同時運行,此時main函數(shù)會繼續(xù)向下執(zhí)行,發(fā)現(xiàn)是從管道中讀取一個元素,然后就會等待demo函數(shù)會向管道中傳入值,demo函數(shù)的運行過程是這樣的,它發(fā)現(xiàn)管道a2的緩存是2,所以剛好把元素存入,然后就執(zhí)行關(guān)閉管道,然后demo協(xié)程銷毀
3.main函數(shù)繼續(xù)執(zhí)行,接收到a2管道的一個元素之后,然后返回value2和ok2,然后進行打印
4.然后休眠3秒鐘
5.然后繼續(xù)讀取a2管道的元素,得到value3和ok3,然后打印
ok2和ok3都為true
’ 管道已經(jīng)關(guān)閉 ’ 這是最先打印的,無論運行多少次,都是一樣的,而且我還特地將main函數(shù)暫停了3秒,所以我可以保證demo函數(shù)已經(jīng)執(zhí)行完畢,demo協(xié)程已經(jīng)銷毀,然后再執(zhí)行的第二個管道的數(shù)據(jù)的讀取
逐步調(diào)試
那我們調(diào)試一下,可以發(fā)現(xiàn),執(zhí)行了make函數(shù)創(chuàng)建管道之后,管道沒有關(guān)閉,我前面特意提了管道的數(shù)據(jù)結(jié)構(gòu),其中closed是標識管道是否關(guān)閉的
繼續(xù)調(diào)試,我們可以發(fā)現(xiàn),在讀取完管道a2的第一個值賦值給value2和ok2的時候,此時通道已經(jīng)關(guān)閉
value2的值為1,ok2為true
繼續(xù)調(diào)試,通道還是關(guān)閉狀態(tài),但是ok3的值還是true,看下面的第二張圖
所以讀取管道元素傳來的第二個值,并不是代表管道是否關(guān)閉!
那它代表什么?
其實是代表讀取數(shù)據(jù)是否成功,或者說代表緩存區(qū)是否還有數(shù)據(jù)
首先我們要知道, 關(guān)閉了的管道, 我們還是可以進行讀取的, 這個設(shè)定是因為有緩存的存在, 但是如果管道關(guān)閉了的話,又沒有值,讀取的話,會是類型的默認值和false,也就是讀取未成功
當然如果是緩存區(qū)為0的情況,ok的值和管道的狀態(tài)是一致的
var c = make(chan int) close(c) value, ok := <-c fmt.Printf("value:%v \nok:%v \n", value, ok)
運行結(jié)果:
以上就是詳解go語言判斷管道是否關(guān)閉的常見誤區(qū)的詳細內(nèi)容,更多關(guān)于go判斷管道是否關(guān)閉的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang使用sqlite3數(shù)據(jù)庫實現(xiàn)CURD操作
這篇文章主要為大家詳細介紹了Golang使用sqlite3數(shù)據(jù)庫實現(xiàn)CURD操作的相關(guān)知識,文中的示例代碼簡潔易懂,有需要的小伙伴可以參考一下2025-03-03Golang學習筆記之安裝Go1.15版本(win/linux/macos/docker安裝)
這篇文章主要介紹了Golang學習筆記之安裝Go1.15版本(win/linux/macos/docker安裝),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12實現(xiàn)像php一樣方便的go ORM數(shù)據(jù)庫操作示例詳解
這篇文章主要為大家介紹了實現(xiàn)像php一樣方便的go ORM數(shù)據(jù)庫操作示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12