|
一 Go并發(fā)模型 傳統(tǒng)的編程語(yǔ)言C++ Java Python等,他們的并發(fā)邏輯多事基于操作系統(tǒng)的線程。并發(fā)執(zhí)行單元(線程)之間的通信利用的就是操作系統(tǒng)提供的線程或進(jìn)程間通信的原語(yǔ)。如:共享內(nèi)存、信號(hào)、管道、消息隊(duì)列、套接字等。在這些通信原語(yǔ)中,使用最廣泛的就是共享內(nèi)存。 如果你使用過(guò)這種共享內(nèi)存的并發(fā)模型,其實(shí)是難用的和容易發(fā)生錯(cuò)誤的,特別是在大型或復(fù)雜的業(yè)務(wù)場(chǎng)景中。 Go語(yǔ)言從程序設(shè)計(jì)當(dāng)初,就將解決上面?zhèn)鹘y(tǒng)并發(fā)模型問(wèn)題作為目標(biāo),并在新并發(fā)模型設(shè)計(jì)中借鑒注明的CSP(Communicationing Sequential Processes-通信順序進(jìn)程)并發(fā)模型。 CSP模型目的在于簡(jiǎn)化并發(fā)程序的編寫(xiě),讓并發(fā)程序的編寫(xiě)順序與編寫(xiě)順序程序一樣簡(jiǎn)單。 生產(chǎn)者 —》輸出數(shù)據(jù) — 輸入/輸出原語(yǔ) —》輸出數(shù)據(jù) 為了實(shí)現(xiàn)CSP模型,GO語(yǔ)言引入了Channel.Goroutine可以讀寫(xiě)channel中的數(shù)據(jù),通過(guò)channel將goroutine組合連接在一起。 Go語(yǔ)言中CSP雖然是主流并發(fā)模型,但是還是支持共享內(nèi)存并發(fā)模型。主要是在sync包中的互斥鎖、讀寫(xiě)鎖、條件變量、原子操作等。那么我們?cè)撊绾芜x擇呢? 第一種:創(chuàng)建模式 通常會(huì)使用下面的方式: 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('到下班時(shí)間了...') }) <-c } Do函數(shù)內(nèi)部創(chuàng)建了一個(gè)gorutine并且返回了一個(gè)channel類(lèi)型的變量。Do函數(shù)創(chuàng)建的新goroutine與調(diào)用的Do函數(shù)的goroutine之間通過(guò)一個(gè)channel聯(lián)系了起來(lái),2個(gè)goroutine可以通過(guò)channel進(jìn)行通訊。Do函數(shù)的實(shí)現(xiàn)因?yàn)?span>channel在Go語(yǔ)言中是一等公民,channel可以像變量一樣初始化、傳遞和賦值。上面的例子Do返回了一個(gè)變量,這個(gè)變量就是通道,實(shí)現(xiàn)了主goroutine和子goroutine的通信。 第二種:退出模式 a) 分離模式 分離模式使用最廣泛的是goroutine退出模式。所謂分離模式就是創(chuàng)建它的goroutine不需要關(guān)心它的退出,這類(lèi)goroutine啟動(dòng)后與其創(chuàng)建者徹底分離,其生命周期與其執(zhí)行的主函數(shù)相關(guān),函數(shù)返回即goroutine退出。 場(chǎng)景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)建了一個(gè)goroutine,用來(lái)監(jiān)聽(tīng)量個(gè)channel是否有數(shù)據(jù),一旦有數(shù)據(jù),處理后即退出。 場(chǎng)景2 常駐后臺(tái)執(zhí)行一些特定任務(wù),比如常用for{…}或for{select{…}}形式,還可以用定時(shí)器或事件驅(qū)動(dòng)執(zhí)行。下面是Go給每個(gè)P內(nèi)置的GC goroutine就是這種場(chǎng)景的。 // $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) // 這里每個(gè)P創(chuàng)建一個(gè)goroutine,以運(yùn)行gcBgMarkWorker notetsleepg(&work.bgMarkReady, -1) noteclear(&work.bgMarkReady) } } } func gcBgMarkWorker(_p_ *p) { gp := getg() ... ... for { // 處理GC ... ... } } b) join模式 在線程模型中,父線程可以通過(guò)pthread join來(lái)等待子線程結(jié)束并獲取子線程的結(jié)束狀態(tài)。在Go中,我們有時(shí)候也有這種需求: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('到下班時(shí)間了...') }) <-c } 我們還是看剛剛上面的這個(gè)例子,Do函數(shù)使用典型的goroutine的創(chuàng)建模式創(chuàng)建了一個(gè)groutine,main的goroutine作為創(chuàng)建通過(guò)Do函數(shù)返回的channel與新goroutine建立關(guān)系,這個(gè)channel得用途就是在goroutine之間建立退出時(shí)間的“信號(hào)”通信機(jī)制。main goroutine在創(chuàng)建完新goroutine后就在該channel上阻塞等待了,直到新的goroutine退出前向該channel發(fā)送了一個(gè)”信號(hào)”。 運(yùn)行代碼,結(jié)果如下: 到下班時(shí)間了... Process finished with exit code 0 獲取goroutine的退出狀態(tài) 如果新goroutine的創(chuàng)建者不僅僅要等待goroutine的退出,還要知道結(jié)束狀態(tài),我們可以通過(guò)自定義類(lèi)型的channel來(lái)實(shí)現(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) } 運(yùn)行結(jié)果是 6 等待多個(gè)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) } 運(yùn)行結(jié)果 6 6 6 6 6 100 c) notify-wait模式 前面的場(chǎng)景中,goroutine的創(chuàng)建者都是在被動(dòng)地等待新goroutine的退出。有些場(chǎng)景,goroutine的創(chuàng)建者需要主動(dòng)通知那些新goroutine退出。 通知并等待一個(gè)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('開(kāi)始干活') 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é)果如下 開(kāi)始干活 0 通知并等待多個(gè)goroutine退出 下面是通知并等待多個(gè)goroutine退出的場(chǎng)景。Go語(yǔ)言的channel有一個(gè)特性,那就是當(dāng)使用close函數(shù)關(guān)閉channel時(shí),所有阻塞到該channel上的goroutine都會(huì)得到通知。 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('等待...') } } 運(yùn)行結(jié)果 func Work... worker-1 done worker-2 done worker-3 done worker-4 done worker-0 done 200 |
|
|