Golang優(yōu)雅關閉channel的方法示例
前言
最近使用go開發(fā)后端服務,服務關閉需要保證channel中的數據都被讀取完,理由很簡單,在收到系統(tǒng)的中斷信號后,系統(tǒng)需要做收尾工作,保證channel的數據都要被處理掉,然后才可以關閉系統(tǒng)。但實現起來沒那么簡單,下面來一起看看詳細的介紹吧。
關于Go channel設計和規(guī)范的批評:
- 在不能更改channel狀態(tài)的情況下,沒有簡單普遍的方式來檢查channel是否已經關閉了
- 關閉已經關閉的channel會導致panic,所以在closer(關閉者)不知道channel是否已經關閉的情況下去關閉channel是很危險的
- 發(fā)送值到已經關閉的channel會導致panic,所以如果sender(發(fā)送者)在不知道channel是否已經關閉的情況下去向channel發(fā)送值是很危險的
所以Golang 內建的 close 方法可以關閉 channel,如果往已經關閉的 channel 發(fā)送數據,則會報錯:panic: close of closed channel.
看如下代碼,在一段時間內,生產者可以不斷往 channel 寫入數據,消費者進行處理,一段時間后 channel 關閉了,這個時候如果還有數據往 channel 發(fā)送,程序就會報錯。
package main
import (
"fmt"
"sync"
"time"
)
func main() {
jobs := make(chan int)
var wg sync.WaitGroup
go func() {
time.Sleep(time.Second * 3)
close(jobs)
}()
go func() {
for i := 0; ; i++ {
jobs <- i
fmt.Println("produce:", i)
}
}()
wg.Add(1)
go func() {
defer wg.Done()
for i := range jobs {
fmt.Println("consume:", i)
}
}()
wg.Wait()
}
多運行幾次出錯的概率會比較大:
produce: 33334 consume: 33334 consume: 33335 produce: 33335 produce: 33336 consume: 33336 consume: 33337 produce: 33337 produce: 33338 consume: 33338 consume: 33339 produce: 33339 produce: 33340 consume: 33340 panic: send on closed channel goroutine 19 [running]: panic(0x49b660, 0xc042410bb0) C:/Go/src/runtime/panic.go:500 +0x1af main.main.func2(0xc04203a180) C:/Users/tanteng/Go/src/examples/channel_close.go:18 +0x6b created by main.main C:/Users/tanteng/Go/src/examples/channel_close.go:21 +0xb8 exit status 2
如何優(yōu)雅關閉 channel
那么在往通道發(fā)數據前如何判斷通道是否關閉呢?
1._,ok := <- jobs
此時如果 channel 關閉,ok 值為 false,如果 channel 沒有關閉,則會漏掉一個 jobs
2.使用 select 方式
再創(chuàng)建一個 channel,叫做 timeout,如果超時往這個 channel 發(fā)送 true,在生產者發(fā)送數據給 jobs 的 channel,用 select 監(jiān)聽 timeout,如果超時則關閉 jobs 的 channel.
完整代碼如下:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
jobs := make(chan int)
timeout := make(chan bool)
var wg sync.WaitGroup
go func() {
time.Sleep(time.Second * 3)
timeout <- true
}()
go func() {
for i := 0; ; i++ {
select {
case <-timeout:
close(jobs)
return
default:
jobs <- i
fmt.Println("produce:", i)
}
}
}()
wg.Add(1)
go func() {
defer wg.Done()
for i := range jobs {
fmt.Println("consume:", i)
}
}()
wg.Wait()
}
這樣就可以保證不會往已經關閉的 channel 中發(fā)送數據了。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關文章
GoFrame框架gset使用對比PHP?Java?Redis優(yōu)勢
這篇文章主要為大家介紹了GoFrame框架gset對比PHP?Java?Redis的使用優(yōu)勢詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06
golang跳轉語句goto,break,continue的使用及區(qū)別說明
這篇文章主要介紹了golang跳轉語句goto,break,continue的使用及區(qū)別說明,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12

