一 Go并發(fā)模型 傳統(tǒng)的編程語言C++ Java Python等,他們的并發(fā)邏輯多事基于操作系統(tǒng)的線程。并發(fā)執(zhí)行單元(線程)之間的通信利用的就是操作系統(tǒng)提供的線程或進程間通信的原語。如:共享內(nèi)存、信號、管道、消息隊列、套接字等。在這些通信原語中,使用最廣泛的就是共享內(nèi)存。 如果你使用過這種共享內(nèi)存的并發(fā)模型,其實是難用的和容易發(fā)生錯誤的,特別是在大型或復雜的業(yè)務(wù)場景中。 Go語言從程序設(shè)計當初,就將解決上面?zhèn)鹘y(tǒng)并發(fā)模型問題作為目標,并在新并發(fā)模型設(shè)計中借鑒注明的CSP(Communicationing Sequential Processes-通信順序進程)并發(fā)模型。 CSP模型目的在于簡化并發(fā)程序的編寫,讓并發(fā)程序的編寫順序與編寫順序程序一樣簡單。 生產(chǎn)者 —》輸出數(shù)據(jù) — 輸入/輸出原語 —》輸出數(shù)據(jù) 為了實現(xiàn)CSP模型,GO語言引入了Channel.Goroutine可以讀寫channel中的數(shù)據(jù),通過channel將goroutine組合連接在一起。 Go語言中CSP雖然是主流并發(fā)模型,但是還是支持共享內(nèi)存并發(fā)模型。主要是在sync包中的互斥鎖、讀寫鎖、條件變量、原子操作等。那么我們該如何選擇呢? 第一種:創(chuàng)建模式 通常會使用下面的方式: type Worker struct { } func Do(f func()) chan Worker { w:= make(chan Worker) go func() { f() w<-Worker{} }() return w } func main() { c:=Do(func() { fmt.Print('到下班時間了...') }) <-c } Do函數(shù)內(nèi)部創(chuàng)建了一個gorutine并且返回了一個channel類型的變量。Do函數(shù)創(chuàng)建的新goroutine與調(diào)用的Do函數(shù)的goroutine之間通過一個channel聯(lián)系了起來,2個goroutine可以通過channel進行通訊。Do函數(shù)的實現(xiàn)因為channel在Go語言中是一等公民,channel可以像變量一樣初始化、傳遞和賦值。上面的例子Do返回了一個變量,這個變量就是通道,實現(xiàn)了主goroutine和子goroutine的通信。 第二種:退出模式 a) 分離模式 分離模式使用最廣泛的是goroutine退出模式。所謂分離模式就是創(chuàng)建它的goroutine不需要關(guān)心它的退出,這類goroutine啟動后與其創(chuàng)建者徹底分離,其生命周期與其執(zhí)行的主函數(shù)相關(guān),函數(shù)返回即goroutine退出。 場景1:一次性任務(wù) // $GOROOT/src/net/dial.go func (d *Dialer) DialContext(ctx context.Context, network, address string) (Conn, error) { ... ... if oldCancel := d.Cancel; oldCancel != nil { subCtx, cancel := context.WithCancel(ctx) defer cancel() go func() { select { case <-oldCancel: cancel() case <-subCtx.Done(): } }() ctx = subCtx } ... ... } 在DialContext方法中創(chuàng)建了一個goroutine,用來監(jiān)聽量個channel是否有數(shù)據(jù),一旦有數(shù)據(jù),處理后即退出。 場景2 常駐后臺執(zhí)行一些特定任務(wù),比如常用for{…}或for{select{…}}形式,還可以用定時器或事件驅(qū)動執(zhí)行。下面是Go給每個P內(nèi)置的GC goroutine就是這種場景的。 // $GOROOT/src/runtime/mgc.go func gcBgMarkStartWorkers() { // Background marking is performed by per-P G's. Ensure that // each P has a background GC G. for _, p := range allp { if p.gcBgMarkWorker == 0 { go gcBgMarkWorker(p) // 這里每個P創(chuàng)建一個goroutine,以運行gcBgMarkWorker notetsleepg(&work.bgMarkReady, -1) noteclear(&work.bgMarkReady) } } } func gcBgMarkWorker(_p_ *p) { gp := getg() ... ... for { // 處理GC ... ... } } b) join模式 在線程模型中,父線程可以通過pthread join來等待子線程結(jié)束并獲取子線程的結(jié)束狀態(tài)。在Go中,我們有時候也有這種需求:goroutine的創(chuàng)建者需要等待新goroutine的結(jié)果。 type Worker struct { } func Do(f func()) chan Worker { w:= make(chan Worker) go func() { f() w<-Worker{} }() return w } func main() { c:=Do(func() { fmt.Print('到下班時間了...') }) <-c } 我們還是看剛剛上面的這個例子,Do函數(shù)使用典型的goroutine的創(chuàng)建模式創(chuàng)建了一個groutine,main的goroutine作為創(chuàng)建通過Do函數(shù)返回的channel與新goroutine建立關(guān)系,這個channel得用途就是在goroutine之間建立退出時間的“信號”通信機制。main goroutine在創(chuàng)建完新goroutine后就在該channel上阻塞等待了,直到新的goroutine退出前向該channel發(fā)送了一個”信號”。 運行代碼,結(jié)果如下: 到下班時間了... Process finished with exit code 0 獲取goroutine的退出狀態(tài) 如果新goroutine的創(chuàng)建者不僅僅要等待goroutine的退出,還要知道結(jié)束狀態(tài),我們可以通過自定義類型的channel來實現(xiàn)這樣的需求。 func add(a,b int) int{ return a+b } func Do(f func(a,b int) int,a,b int) chan int{ c:=make(chan int) go func() { r:=f(a,b) c<-r }() return c } func main() { c:=Do(add,1,5) fmt.Println(<-c) } 運行結(jié)果是 6 等待多個goroutine退出 func add(a,b int) int{ return a+b } func Do(f func(a,b int) int,a,b,n int) chan int{ c:=make(chan int) var wg sync.WaitGroup for i:=0;i<n;i++{ wg.Add(1) go func() { r:=f(a,b) fmt.Println(r) wg.Done() }() } go func() { wg.Wait() c<-100 }() go func() { }() return c } func main() { c:=Do(add,1,5,5) fmt.Println(<-c) } 運行結(jié)果 6 6 6 6 6 100 c) notify-wait模式 前面的場景中,goroutine的創(chuàng)建者都是在被動地等待新goroutine的退出。有些場景,goroutine的創(chuàng)建者需要主動通知那些新goroutine退出。 通知并等待一個goroutine的退出 func add(a, b int) int { return a + b } func Do(f func(a, b int) int, a, b int) chan int { quit := make(chan int) go func() { var job chan string for { select { case x := <-job: f(a, b) fmt.Println(x) case y := <-quit: quit <- y } } }() return quit } func main() { c := Do(add, 1, 5) fmt.Println('開始干活') time.Sleep(1 * time.Second) c <- 0 timer := time.NewTimer(time.Second * 10) defer timer.Stop() select { case status := <-c: fmt.Println(status) case <-timer.C: fmt.Println('等待...') } } 執(zhí)行代碼結(jié)果如下 開始干活 0 通知并等待多個goroutine退出 下面是通知并等待多個goroutine退出的場景。Go語言的channel有一個特性,那就是當使用close函數(shù)關(guān)閉channel時,所有阻塞到該channel上的goroutine都會得到通知。 func worker(x int) { time.Sleep(time.Second * time.Duration(x)) } func Do(f func(a int), n int) chan int { quit := make(chan int) job:=make(chan int) var wg sync.WaitGroup for i:=0;i<n;i++ { wg.Add(1) go func(i int) { defer wg.Done() name := fmt.Sprintf('worker-%d',i) for { j,ok:=<-job if !ok{ fmt.Println(name,'done') return } worker(j) } }(i) } go func() { <-quit close(job) wg.Wait() quit<-200 }() return quit } func main() { quit:=Do(worker,5) fmt.Println('func Work...') quit<-1 timer := time.NewTimer(time.Second * 10) defer timer.Stop() select { case status := <-quit: fmt.Println(status) case <-timer.C: fmt.Println('等待...') } } 運行結(jié)果 func Work... worker-1 done worker-2 done worker-3 done worker-4 done worker-0 done 200 |
|