管道(Channel)的讀取與寫入「讓我們一起Golang」
我們都知道,協程是通過管道來進行通信、排程的。是以接下來我們引入管道的概念,通過管道可以來傳遞資料,協程與協程之間也可以通過管道來進行排程。
先來看一看第一段代碼:
func main() {
//建立一個管道
ch := make(chan int)
//子協程讀資料
go func() {
x := <-ch
fmt.Println("從管道内讀資料:",x)
}()
//因為ch是int整形管道,往管道ch内隻可寫入整形資料,例如123。
//主協程寫資料
ch <- 123
time.Sleep(time.Second)
fmt.Println("GAMEOVER")
}
這裡是建立一個管道,然後用主協程往管道内寫資料,然後從子協程往管道内讀資料。
建立管道是用make,第一部分是
chan
,第二個部分是管道内資料的資料類型。因為沒有給管道制定長度,是以預設為0。是以不能用于緩存。
該段程式是主協程往管道内寫入123,然後子協程從管道内讀出123.
運作結果是:
從管道内讀資料: 123
GAMEOVER
那麼思考一下,如果此段代碼中将
time.Sleep(time.Second)
注釋掉,也就是不使用這句代碼不讓主協程睡一秒,會出現什麼情況?
是的,如果主協程不睡一秒的話,子協程可能還沒讀到資料,主協程就結束了,注意,主協程不是被殺死了,是正常結束了。
如果不睡一秒,而是使用
runtime.Goexit()
殺掉主協程,那麼子協程就會失去限制,仍然會輸出管道的資料。
如果主協程不寫的話,我們從管道中讀不到資料這樣可以了解,但是你可能想不到的是,如果子協程不讀的話,主協程也不能将資料成功寫入管道中。
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
它會報一個緻命錯誤,它預判會産生死鎖。
這說明管道不能存東西,它是沒有緩存能力的,隻能用于傳輸資料。
下面來講一講管道關閉之後的讀寫。
//定義管道
var ch chan int
//make才能初始化
ch := make(chan int)
//關閉管道
close(ch)
如果隻定義管道,那麼管道
ch
為nil,此時不能關閉管道,此時關閉管道會報錯。
隻有将管道
ch
初始化之後,才能正常關閉管道。
不能重複關閉管道。
關閉會報錯:
panic: close of closed channel
我們再來看一看關閉管道後再來讀會怎麼樣咯~
func main() {
//定義管道
var ch chan int
//初始化管道,緩存能力為3
ch = make(chan int , 3)
ch <- 123
//關閉管道
close(ch)
go func() {
x := <-ch
fmt.Println("讀到",x)
//x,ok := <-ch
//fmt.Println("讀到",x,ok)
}()
time.Sleep(time.Second)
fmt.Println("GAME OVER")
}
此段代碼主協程中先關閉管道,然後再開辟子協程來讀取管道中的資料。能不能讀到呢?
先看一看結果:
讀到 123
GAME OVER
讀到了!
因為我們給管道的第二個參數設定為3,這就讓管道有了緩存能力。而關閉管道之前已經将資料123存入了管道,之後再讀取管道内資料是能夠讀取到的。
可以如果我們讀取之後,再讀一遍呢?會怎麼樣呢?
我們激活下面這段代碼
x,ok := <-ch
fmt.Println("讀到",x,ok)
得到的運作結果是:
讀到 123讀到 0 falseGAME OVER
則說明讀取管道内的資料之後繼續再讀一遍是讀到的預設資料0,無論你再讀多少遍,讀到都還是0。
如果管道關閉,我們還能像裡面寫資料嗎?
是的,不能再往管道寫資料了。
如果我們往管道裡面寫n個,那麼關閉管道之後,再去讀管道,就能再讀n個,超過n個就讀到的是預設值0。