這個是前段時間看到Go語言的貢獻者與佈道師 Dave Cheney對Go併發的建議或者叫使用的陷阱(不是我自己的建議),結合自己最近幾年對gorotine的使用,再回頭看這幾條建議,真的會茅塞頓開,覺得特別重要。這篇文章對併發的建議的章節地址
https://dave.cheney.net/practical-go/presentations/qcon-china.html#_concurrency
Dave Cheney
Dave Cheney 是 Go 程式語言的開源貢獻者和專案成員。David 是技術社群中備受尊敬的聲音,他就軟體設計、效能和 Go 程式語言等各種主題發表演講。David 在go語言歷程中,分享過很多關於Golang語言的正確使用的文章。這是他的部落格地址。
https://dave.cheney.net/
8.1. Keep yourself busy or do the work yourself(讓自己忙碌起來或自己做工作)
這個建議應該比較容易理解,啟動一個gorotine應該是執行程式的,自己執行或者被人呼叫執行,不應該啟動gorotine之後這個gorotine啥事都沒幹。
作者舉了一個例子
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, GopherCon SG")
})
go func() {
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}()
for {
}
}
為了阻塞main gorotine不要直接退出,等待go func的執行,最後寫了一個for的死迴圈,這樣的話,main gorotine就是通常所說的啥事都沒幹,毫無結果地執行。我們當然可以使用WaitGroup去等待go func的結束。作者給我們的建議,既然我們只有一個任務需要做,main gorotine就可以完成,為什麼要啟動一個gorotine去做這個任務,而讓main gorotine去等待,完全可以讓main去做這個任務
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, GopherCon SG")
})
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}
許多 Go 程式設計師過度使用 goroutine,尤其是在他們剛開始的時候。與生活中的所有事物一樣,適度是成功的關鍵。
8.2. Leave concurrency to the caller(將併發留給呼叫者)
這個表述起來比較容易,平常開發中可能會被忽略,一個物件提供了啟動使用goroutine的方法,那麼就必須提供關閉goroutine的方法,而且一般得原則的是誰呼叫誰關閉。
舉一個我們專案開發中的例子
timer_go.go
package main
import (
"fmt"
"sync"
"time"
)
type TimerGo struct {
quit chan bool
}
func NewTimerGo() *TimerGo {
timer := new(TimerGo)
timer.quit = make(chan bool)
return timer
}
func (this *TimerGo) Run(wg *sync.WaitGroup) {
defer wg.Done()
cfgTime := 3
t := time.NewTicker(time.Duration(cfgTime) * time.Second)
defer t.Stop()
for {
select {
case <- this.quit:
fmt.Println("quite")
return
case <-t.C:
this.Sync()
}
}
}
func (this *TimerGo) Sync() {
fmt.Printf("Sync")
}
func (this *TimerGo) Close() {
close(this.quit)
}
main.go
package main
func main() {
timergo := NewTimerGo()
wg := new(sync.WaitGroup)
wg.Add(1)
go timergo.Run(wg)
//start up
timergo.Close()
wg.Wait()
}
這個例子比較容易理解,我們需要每隔三秒執行一個非同步的任務,這個工作我們啟動一個goroutine去執行,所以我們在main函式執行go timergo.Run,我們也提供Close的方法,通過一個channal去關閉它。
原則就是,誰呼叫誰關閉。提供執行方法,就必須提供關閉方法。
8.3. Never start a goroutine without knowning when it will stop(永遠不要在不知道何時停止的情況下啟動 goroutine)
這個原則我覺得應該是最重要的原則,而且在開發中最容易遇到的問題。我們前期也寫過很多這樣的程式碼,而且我看大家使用的專案基本也都是在需要啟動一個goroutine去執行程式碼的時候是這樣寫的
go AAA()
go BBB()
go CCC()
很少有人去關心啟動的這三個goroutine應該在什麼情況下去關閉,應該怎麼關閉,他們得執行狀態是怎麼樣的,在服務重新啟動時候,是等待執行完畢還是強制中斷。
這個原則應該會指引我們去做一些可靠的架構和規劃。這個遇到的太多了,有必要花時間去整理這裡。
永遠不要在不知道何時停止的情況下啟動 goroutine