Go編程技巧--Goroutine的優雅控制

原文:Go編程技巧--Goroutine的優雅控制html

Goroutine是Go語言最重要的機制,Goroutine將複雜的須要異步的IO調用抽象成同步調用,符合人類正常的順序思惟,極大的簡化了IO編程的難度。如同線程同樣,對Goroutine既要掌握基本的用法,更要很好的控制Goroutine的退出機制。本文介紹一種Goroutine的退出思路。編程

一般Goroutine會由於兩種狀況阻塞:併發

  1. IO操做,好比對SocketRead異步

  2. channel操做。對一個chan的讀寫都有可能阻塞Goroutine函數

對於狀況1,只須要關閉對應的描述符,阻塞的Goroutine天然會被喚醒。oop

重點討論狀況2。併發編程,Goroutine提供一種channel機制,channel相似管道,寫入者向裏面寫入數據,讀取者從中讀取數據。若是channel裏面沒有數據,讀取者將阻塞,直到有數據;若是channel裏面數據滿了,寫入者將由於沒法繼續寫入數據而阻塞。線程

若是在整個應用程序的生命週期裏,writer和reader都表現爲一個Goroutine,始終都在工做,那麼如何在應用程序結束前,通知它們終止呢?在Go中,並不推薦像abort線程那樣,強行的終止Goroutine。所以,抽象的說,必然須要保留一個入口,可以跟writer或reader通訊,以告知它們終止。設計

咱們先看reader。咱們首先能夠想到,利用close函數關閉正在讀取的channel,從而能夠喚醒reader,並退出。可是考慮到close並不能很好的處理writer(由於writer試圖寫入一個已經close的channel,將引起異常)。所以,咱們須要設計一個額外的只讀channel用於通知:code

type routineSignal struct {
    done <-chan struct{}
}

routineSignal的實例,應當經過外部生成並傳遞給reader,例如:htm

func (r *reader)init(s *routineSignal) {
    r.signal = s
}

在reader的循環中,就能夠這麼寫:

func (r *reader)loop() {
    for {
        select {
        case <-r.signal.done:
            return
        case <-r.queue:
            ....
        }
    }
}

當須要終止Goroutine的時候只須要關閉這個額外的channel

close(signal.done)

看起來很完備了,這能夠處理大部分的狀況了。這樣作有個弊端,儘管,咱們能夠指望close喚醒Goroutine進而退出,可是並不能知道Goroutine何時完成退出,由於Goroutine可能在退出前還有一些善後工做,這個時候咱們須要sync.WaitGroup。改造一下routineSignal

type routineSignal struct {
    done chan struct{}
    wg   sync.WaitGroup
}

增長一個sync.WaitGroup的實例,在Goroutine開始工做時,對wg加1,在Goroutine退出前,對wg減1:

func (r *reader)loop() {
    r.signal.wg.Add(1)
    defer r.signal.wg.Done()
    for {
        select {
        case <-r.signal.done:
            return
        case <-r.queue:
            ....
        }
    }
}

外部,只須要等待WaitGroup返回便可:

close(signal.done)
signal.wg.Wait()

只要Wait()返回就能判定Goroutine結束了。

推導一下,不難發現,對於writer也能夠採用這種方法。因而,總結一下,咱們建立了一個叫routineSignal的結構,結構裏面包含一個chan用來通知Goroutine結束,包含一個WaitGroup用於Goroutine通知外部完成善後。這樣,經過這個結構的實例優雅的終止Goroutine,並且還能夠確保Goroutine終止成功。

相關文章
相關標籤/搜索