詳解go語言判斷管道是否關(guān)閉的常見誤區(qū)
前言
本文是探討的是"在Go語言中,我們是否可以使用讀取管道時(shí)的第二個(gè)返回值來判斷管道是否關(guān)閉?"
樣例
在Go語言中,我們是否可以使用讀取管道時(shí)的第二個(gè)返回值來判斷管道是否關(guān)閉? 可以看下面的代碼
package main
import "fmt"
func main() {
// 創(chuàng)建一個(gè)整型管道
ch := make(chan int)
// 啟動(dòng)一個(gè)協(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)
在探討這個(gè)問題之前,我們先來了解一下管道的數(shù)據(jù)結(jié)構(gòu),從go的源碼,我們可以知道,管道是被定義為一個(gè)名為hchan的結(jié)構(gòu)體:
type hchan struct {
qcount uint //當(dāng)前隊(duì)列中剩余的元素個(gè)數(shù)
dataqsiz uint // 環(huán)形隊(duì)列管道容積
buf unsafe.Pointer // 環(huán)形隊(duì)列指針
elemsize uint16 // 元素大小
closed uint32 // 標(biāo)識(shí)管道關(guān)閉的狀態(tài)
elemtype *_type // 元素類型
recvq waitq // 等待的讀元素的協(xié)程隊(duì)列
sendq waitq // 等待的寫元素的協(xié)程隊(duì)列
...
}其中,有一個(gè)屬性是我們應(yīng)該關(guān)注的,那就是closed,這玩意標(biāo)識(shí)了管道是否關(guān)閉,這玩意為1代表關(guān)閉了,為0代表是開啟的.
詳細(xì)分析
好的,接下來我們繼續(xù)本文探討的問題在Go語言中,我們是否可以使用管道的第二個(gè)返回值來判斷管道是否關(guān)閉?先給出結(jié)論 : 從嚴(yán)格意義上來講是不可以的,其實(shí)表示是否成功讀取數(shù)據(jù),但是在緩存區(qū)為0的時(shí)候,ok的狀態(tài)和管道狀態(tài)是一致的,所以會(huì)被誤認(rèn)為,這個(gè)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
}解釋一下運(yùn)行流程
1.首先創(chuàng)建了一個(gè)緩存區(qū)為2的管道a2
2.然后用go關(guān)鍵字
將demo函數(shù)開辟出一個(gè)新的協(xié)程運(yùn)行,此時(shí)demo和main是同一級(jí)的關(guān)系,同時(shí)運(yùn)行,此時(shí)main函數(shù)會(huì)繼續(xù)向下執(zhí)行,發(fā)現(xiàn)是從管道中讀取一個(gè)元素,然后就會(huì)等待demo函數(shù)會(huì)向管道中傳入值,demo函數(shù)的運(yùn)行過程是這樣的,它發(fā)現(xiàn)管道a2的緩存是2,所以剛好把元素存入,然后就執(zhí)行關(guān)閉管道,然后demo協(xié)程銷毀
3.main函數(shù)繼續(xù)執(zhí)行,接收到a2管道的一個(gè)元素之后,然后返回value2和ok2,然后進(jìn)行打印
4.然后休眠3秒鐘
5.然后繼續(xù)讀取a2管道的元素,得到value3和ok3,然后打印

ok2和ok3都為true
’ 管道已經(jīng)關(guān)閉 ’ 這是最先打印的,無論運(yùn)行多少次,都是一樣的,而且我還特地將main函數(shù)暫停了3秒,所以我可以保證demo函數(shù)已經(jīng)執(zhí)行完畢,demo協(xié)程已經(jīng)銷毀,然后再執(zhí)行的第二個(gè)管道的數(shù)據(jù)的讀取
逐步調(diào)試
那我們調(diào)試一下,可以發(fā)現(xiàn),執(zhí)行了make函數(shù)創(chuàng)建管道之后,管道沒有關(guān)閉,我前面特意提了管道的數(shù)據(jù)結(jié)構(gòu),其中closed是標(biāo)識(shí)管道是否關(guān)閉的

繼續(xù)調(diào)試,我們可以發(fā)現(xiàn),在讀取完管道a2的第一個(gè)值賦值給value2和ok2的時(shí)候,此時(shí)通道已經(jīng)關(guān)閉

value2的值為1,ok2為true

繼續(xù)調(diào)試,通道還是關(guān)閉狀態(tài),但是ok3的值還是true,看下面的第二張圖


所以讀取管道元素傳來的第二個(gè)值,并不是代表管道是否關(guān)閉!
那它代表什么?
其實(shí)是代表讀取數(shù)據(jù)是否成功,或者說代表緩存區(qū)是否還有數(shù)據(jù)
首先我們要知道, 關(guān)閉了的管道, 我們還是可以進(jìn)行讀取的, 這個(gè)設(shè)定是因?yàn)橛芯彺娴拇嬖? 但是如果管道關(guān)閉了的話,又沒有值,讀取的話,會(huì)是類型的默認(rèn)值和false,也就是讀取未成功
當(dāng)然如果是緩存區(qū)為0的情況,ok的值和管道的狀態(tài)是一致的
var c = make(chan int)
close(c)
value, ok := <-c
fmt.Printf("value:%v \nok:%v \n", value, ok)運(yùn)行結(jié)果:

以上就是詳解go語言判斷管道是否關(guān)閉的常見誤區(qū)的詳細(xì)內(nèi)容,更多關(guān)于go判斷管道是否關(guān)閉的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang使用sqlite3數(shù)據(jù)庫(kù)實(shí)現(xiàn)CURD操作
這篇文章主要為大家詳細(xì)介紹了Golang使用sqlite3數(shù)據(jù)庫(kù)實(shí)現(xiàn)CURD操作的相關(guān)知識(shí),文中的示例代碼簡(jiǎn)潔易懂,有需要的小伙伴可以參考一下2025-03-03
Golang學(xué)習(xí)筆記之安裝Go1.15版本(win/linux/macos/docker安裝)
這篇文章主要介紹了Golang學(xué)習(xí)筆記之安裝Go1.15版本(win/linux/macos/docker安裝),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12
輕松構(gòu)建Go應(yīng)用的Dockerfile
本文介紹了如何制作一個(gè)用于構(gòu)建和運(yùn)行Go應(yīng)用程序的Docker鏡像的Dockerfile的相關(guān)資料,需要的朋友可以參考下2023-10-10
實(shí)現(xiàn)像php一樣方便的go ORM數(shù)據(jù)庫(kù)操作示例詳解
這篇文章主要為大家介紹了實(shí)現(xiàn)像php一樣方便的go ORM數(shù)據(jù)庫(kù)操作示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12

