go 關閉channel分析

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

背景

最近使用go開發後端服務,服務關閉需要保證channel中的資料都被讀取完,理由很簡單,在收到系統的中斷訊號後,系統需要做收尾工作,保證channel的資料都要被處理掉,然後才可以關閉系統。

後面我會給出方案,見範例程式碼,但在解決這個問題之前我們先瞭解下close channel的一些特性。

channel

  • 關閉channel
ch := make(chan bool) close(ch) close(ch)  // 這樣會panic的,channel不能close兩次 
  • 向已經關閉的channel寫資料
ch := make(chan string) close(ch) ch <- "good" // 會panic的
  • 從已經關閉的channel讀取資料
    需要分兩種情況:
    • 無緩衝channel或者緩衝channel已經讀取完畢
    • 緩衝channel未讀取完畢,可以繼續讀取channel中的剩餘的資料
//無緩衝channelch := make(chan string) close(ch) i := <- ch // 不會panic, i讀取到的值是空 "",  如果channel是bool的,那麼讀取到的是false 
  • 判斷channel是否關閉
i, ok := <- ch if ok {     println(i) } else {     println("channel closed") } 

方案

我直接上範例程式碼

package mainimport (    "fmt"    "os"    "os/signal"    "sync"    "syscall"    "time"  )func main() {    var wg sync.WaitGroup    ch := make(chan int, 100)    chSend := make(chan int)    chConsume := make(chan int)    sc := make(chan os.Signal, 1)    signal.Notify(sc,        os.Kill,        os.Interrupt,        syscall.SIGHUP,        syscall.SIGINT,        syscall.SIGTERM,        syscall.SIGQUIT)    go func(ch, quit chan int) {        defer func() {            if err := recover(); err != nil {                fmt.Println("send to ch panic.===", err)            }        }()        i := 0        for {            select {            case ch <- i:                fmt.Println("send", i)                time.Sleep(time.Second)                i++            case <-quit:                fmt.Println("send quit.")                return            }        }    }(ch, chSend)    go func(ch, quit chan int) {        wg.Add(1)        for {            select {            case i, ok := <-ch:                if ok {                    fmt.Println("read1", i)                    time.Sleep(time.Second * 2)                } else {                    fmt.Println("close ch1.")                }            case <-quit:                for {                    select {                    case i, ok := <-ch:                        if ok {                            fmt.Println("read2", i)                            time.Sleep(time.Second * 2)                        } else {                            fmt.Println("close ch2.")                            goto L                        }                    }                }            L:                fmt.Println("consume quit.")                wg.Done()                return            }        }    }(ch, chConsume)    <-sc    close(ch)    fmt.Println("close ch ")    close(chSend)    close(chConsume)    wg.Wait()}

輸出結果:
send 0
read1 0
send 1
send 2
read1 1
send 3
send 4
read1 2
send 5
close ch
send quit.
read1 3
read2 4
read2 5
close ch2.
consume quit.

說明
收到中斷訊號後,會關閉帶緩衝的channel ch、無緩衝的chSend、chConsume.從列印的日誌可以看出 close ch之後,send的goroutine就結束了(可能列印send quit,也可能列印send to ch panic,可以多執行幾次就會發現這種情況,原因就是select case有多個case滿足條件會隨機執行一個case),此時還可以從ch繼續讀取channel中的資料(列印了read1 3 read2 4 read2 5),後面就列印了close ch2 說明ok是false,此時才知道ch已經關閉。通過這個特性可以很優雅的關閉服務。

如果服務被強行kill掉或者機器異常等情況,channel中的未讀取資料還是會丟失,系統設計需要允許這種情況的發生

後話

說實話,上面樣本的做法雖然能達到安全關閉服務的效果,但個人覺得實現不夠優雅,具體也說不出為什麼。

如果各位有更好的實現方式,請給我留言,謝謝。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.