golang channel讀取數(shù)據(jù)的幾種情況
用var定義channel且不make
wg := sync.WaitGroup{}
var ch chan string
read := func() {
?? ?fmt.Println("reading")
?? ?s := <-ch
?? ?fmt.Println("read:", s)
?? ?wg.Done()
}
write := func() {
?? ?fmt.Println("writing")
?? ?s := "t"
?? ?ch <- s
?? ?fmt.Println("write:", s)
?? ?wg.Done()
}
wg.Add(2)
go read()
go write()
fmt.Println("waiting")
wg.Wait()輸出:
waiting
writing
reading
fatal error: all goroutines are asleep - deadlock!
這種情況并不是報錯空指針,而是死鎖。加上make看看
用var定義channel且make
wg := sync.WaitGroup{}
var ch = make(chan string)
read := func() {
?? ?fmt.Println("reading")
?? ?s := <-ch
?? ?fmt.Println("read:", s)
?? ?wg.Done()
}
write := func() {
?? ?fmt.Println("writing")
?? ?s := "t"
?? ?ch <- s
?? ?fmt.Println("write:", s)
?? ?wg.Done()
}
wg.Add(2)
go read()
go write()輸出
waiting
writing
reading
read: t
write: t
這種情況沒什么毛病,之所以先輸出的read,是因?yàn)镮O機(jī)制。下面給寫加上for
直給寫操作加for
wg := sync.WaitGroup{}
var ch = make(chan string)
read := func() {
?? ?fmt.Println("reading")
?? ?s := <-ch
?? ?fmt.Println("read:", s)
?? ?wg.Done()
}
write := func() {
?? ?for {
?? ??? ?fmt.Println("writing")
?? ??? ?s := "t"
?? ??? ?ch <- s
?? ??? ?fmt.Println("write:", s)
?? ?}
?? ?wg.Done()
}
wg.Add(2)
go read()
go write()
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")輸出
waiting
reading
writing
write: t
writing
read: t
fatal error: all goroutines are asleep - deadlock!
報錯說所有的協(xié)程都睡著,意思就是runtime發(fā)現(xiàn)沒有能拿來調(diào)度的協(xié)程了,報錯退出。如果是在大項(xiàng)目中,這里則會阻塞,runtime會調(diào)度其他可運(yùn)行的協(xié)程。下面把for移到讀操作上。
直給讀操作加for
wg := sync.WaitGroup{}
var ch = make(chan string)
read := func() {
?? ?for {
?? ??? ?fmt.Println("reading")
?? ??? ?s := <-ch
?? ??? ?fmt.Println("read:", s)
?? ?}
?? ?wg.Done()
}
write := func() {
?? ?fmt.Println("writing")
?? ?s := "t"
?? ?ch <- s
?? ?fmt.Println("write:", s)
?? ?wg.Done()
}
wg.Add(2)
go read()
go write()
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")輸出
waiting
reading
writing
write: t
read: t
reading
fatal error: all goroutines are asleep - deadlock!
跟上面現(xiàn)象基本一樣,不再贅述,然后給倆操作都加上for
讀寫都加for
wg := sync.WaitGroup{}
var ch = make(chan string)
read := func() {
?? ?for {
?? ??? ?fmt.Println("reading")
?? ??? ?s := <-ch
?? ??? ?fmt.Println("read:", s)
?? ?}
?? ?wg.Done()
}
write := func() {
?? ?for {
?? ??? ?fmt.Println("writing")
?? ??? ?s := "t"
?? ??? ?ch <- s
?? ??? ?fmt.Println("write:", s)
?? ?}
?? ?wg.Done()
}
wg.Add(2)
go read()
go write()
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")輸出
waiting
writing
reading
read: t
write: t
writing
reading
read: t
reading
write: t
writing
write: t
writing
...
結(jié)果當(dāng)然就是死循環(huán)了,這個很好理解。接下來才是本文的重點(diǎn):讀數(shù)據(jù)的第二個參數(shù)。我們先保持其他的都不動,在讀的時候接收第二個返回值。
讀channel的第二個返回值
wg := sync.WaitGroup{}
var ch = make(chan string)
read := func() {
?? ?for {
?? ??? ?fmt.Println("reading")
?? ??? ?s, ok := <-ch
?? ??? ?fmt.Println("read:", s, ok)
?? ?}
?? ?wg.Done()
}
write := func() {
?? ?for {
?? ??? ?fmt.Println("writing")
?? ??? ?s := "t"
?? ??? ?ch <- s
?? ??? ?fmt.Println("write:", s)
?? ?}
?? ?wg.Done()
}
wg.Add(2)
go read()
go write()
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")輸出
waiting
writing
reading
read: t true
reading
write: t
writing
write: t
writing
read: t true
reading
read: t true
reading
write: t
...
可以看出來,這第二個返回值是個bool類型,目前全都是true。那么什么時候會是false呢,把channel關(guān)上試試。為了更直觀,把字符串的長度一起輸出
關(guān)閉channel繼續(xù)讀
wg := sync.WaitGroup{}
var ch = make(chan string)
read := func() {
?? ?for {
?? ??? ?fmt.Println("reading")
?? ??? ?s, ok := <-ch
?? ??? ?fmt.Println("read:", len(s), s, ok)
?? ?}
?? ?wg.Done()
}
write := func() {
?? ?for i := 0; i < 5; i++ {
?? ??? ?fmt.Println("writing")
?? ??? ?s := "t"
?? ??? ?ch <- s
?? ??? ?fmt.Println("write:", s)
?? ?}
?? ?wg.Done()
?? ?close(ch)
}
wg.Add(2)
go read()
go write()
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")輸出
waiting
writing
reading
read: 1 t true
reading
write: t
writing
write: t
writing
read: 1 t true
reading
read: 1 t true
reading
write: t
writing
write: t
writing
read: 1 t true
reading
read: 1 t true
reading
write: t
read: 0 false
reading
read: 0 false
reading
read: 0 false
...
接下來就是很規(guī)律的死循環(huán)了。這樣是不是可以猜測,從已經(jīng)close的channle讀數(shù)據(jù),會讀到該數(shù)據(jù)類型的零值,且第二個返回值為false?再試試給channel加個buffer,先寫完關(guān)上再開始讀
寫完然后關(guān)閉channel再開始讀
wg := sync.WaitGroup{}
var ch = make(chan string, 5)
read := func() {
?? ?for {
?? ??? ?fmt.Println("reading")
?? ??? ?s, ok := <-ch
?? ??? ?fmt.Println("read:", len(s), s, ok)
?? ?}
?? ?wg.Done()
}
write := func() {
?? ?for i := 0; i < 5; i++ {
?? ??? ?fmt.Println("writing")
?? ??? ?s := "t"
?? ??? ?ch <- s
?? ??? ?fmt.Println("write:", s)
?? ?}
?? ?wg.Done()
?? ?close(ch)
?? ?fmt.Println("closed")
}
wg.Add(2)
write()
go read()
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")輸出
writing
write: t
writing
write: t
writing
write: t
writing
write: t
writing
write: t
closed
waiting
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 0 false
reading
read: 0 false
reading
read: 0 false
...
我們把寫操作前的go關(guān)鍵字去了,并且在關(guān)閉channel之后加了log。可以很清晰的看到,先往channel里寫了5次,然后close了,之后才有wait及read的log。并且前5個ok是true,后面循環(huán)輸出false?,F(xiàn)在我們可以得出結(jié)論當(dāng)channel關(guān)閉且數(shù)據(jù)都讀完了,再讀數(shù)據(jù)會讀到該數(shù)據(jù)類型的零值,且第二個返回值為false。下面再套上select
加個select
wg := sync.WaitGroup{}
var ch = make(chan string, 5)
read := func() {
?? ?for {
?? ??? ?fmt.Println("reading")
?? ??? ?select {
?? ??? ?case s, ok := <-ch:
?? ??? ??? ?fmt.Println("read:", len(s), s, ok)
?? ??? ?}
?? ?}
?? ?wg.Done()
}
write := func() {
?? ?for i := 0; i < 5; i++ {
?? ??? ?fmt.Println("writing")
?? ??? ?s := "t"
?? ??? ?ch <- s
?? ??? ?fmt.Println("write:", s)
?? ?}
?? ?wg.Done()
?? ?close(ch)
?? ?fmt.Println("closed")
}
wg.Add(2)
write()
go read()
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")輸出
writing
write: t
writing
write: t
writing
write: t
writing
write: t
writing
write: t
closed
waiting
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 0 false
reading
read: 0 false
reading
read: 0 false
...
很明顯跟上面現(xiàn)象一致,如果忘了關(guān)閉channel呢?
channel未及時關(guān)閉
wg := sync.WaitGroup{}
var ch = make(chan string, 5)
read := func() {
?? ?for {
?? ??? ?fmt.Println("reading")
?? ??? ?select {
?? ??? ?case s, ok := <-ch:
?? ??? ??? ?fmt.Println("read:", len(s), s, ok)
?? ??? ?}
?? ?}
?? ?wg.Done()
}
write := func() {
?? ?for i := 0; i < 5; i++ {
?? ??? ?fmt.Println("writing")
?? ??? ?s := "t"
?? ??? ?ch <- s
?? ??? ?fmt.Println("write:", s)
?? ?}
?? ?wg.Done()
?? ?//close(ch)
?? ?//fmt.Println("closed")
}
wg.Add(2)
write()
go read()
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")輸出
writing
write: t
writing
write: t
writing
write: t
writing
write: t
writing
write: t
waiting
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
fatal error: all goroutines are asleep - deadlock!
睡著了,然后報錯。跟上面情況一樣,如果是在大項(xiàng)目中,runtime會調(diào)度其他可運(yùn)行的協(xié)程。最后來總結(jié)一下怎么操作才算優(yōu)(sao)雅(qi)。
總結(jié)
- 對寫的一方來說,一定記著及時關(guān)閉channel,避免出現(xiàn)協(xié)程泄露。雖然它占得資源少,省點(diǎn)電不香么。
- 對讀的一方來說,除非十分確定數(shù)據(jù)的個數(shù),最好是用for來讀數(shù)據(jù),省的在“管兒”里有“野數(shù)據(jù)”造成內(nèi)存泄露。同時根據(jù)第二個返回值的真假來控制for循環(huán),避免出現(xiàn)“無效工作量”
到此這篇關(guān)于golang channel讀取數(shù)據(jù)的幾種情況的文章就介紹到這了,更多相關(guān)golang channel讀取內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Go語言如何實(shí)現(xiàn)并發(fā)安全的map
go語言提供的數(shù)據(jù)類型中,只有channel是并發(fā)安全的,基礎(chǔ)map并不是并發(fā)安全的,本文為大家整理了三種實(shí)現(xiàn)了并發(fā)安全的map的方案,有需要的可以參考下2023-12-12
分析Go語言中CSP并發(fā)模型與Goroutine的基本使用
我們都知道并發(fā)是提升資源利用率最基礎(chǔ)的手段,尤其是當(dāng)今大數(shù)據(jù)時代,流量對于一家互聯(lián)網(wǎng)企業(yè)的重要性不言而喻。串流顯然是不行的,尤其是對于web后端這種流量的直接載體。并發(fā)是一定的,問題在于怎么執(zhí)行并發(fā)。常見的并發(fā)方式有三種,分別是多進(jìn)程、多線程和協(xié)程2021-06-06
一文詳解Golang?定時任務(wù)庫?gron?設(shè)計和原理
這篇文章主要介紹了一文詳解Golang?定時任務(wù)庫?gron?設(shè)計和原理,gron是一個比較小巧、靈活的定時任務(wù)庫,可以執(zhí)行定時的、周期性的任務(wù)。gron提供簡潔的、并發(fā)安全的接口2022-08-08

