相信大家踏入Go
語(yǔ)言的世界,肯定是被強(qiáng)大的并發(fā)(Concurrency
)所吸引,Go
語(yǔ)言用最簡(jiǎn)單的關(guān)鍵字go
就可以將任務(wù)丟到后臺(tái)處理,但是開發(fā)者怎么有效率的控制并發(fā),這是入門Go
語(yǔ)言必學(xué)的技能,本章會(huì)介紹幾種方式來(lái)帶大家認(rèn)識(shí)并發(fā),而這三種方式分別對(duì)應(yīng)到三個(gè)不同的名詞:WaitGroup
,Channel
,及 Context
。下面用簡(jiǎn)單的范例帶大家了解。
WaitGroup
先來(lái)了解有什么情境需要使用到 WaitGroup
,假設(shè)您有兩臺(tái)機(jī)器需要同時(shí)上傳最新的代碼,兩臺(tái)機(jī)器分別上傳完成后,才能執(zhí)行最后的重啟步驟。就像是把一個(gè)工作同時(shí)拆成好幾份同時(shí)一起做,可以減少時(shí)間,但是最后需要等到全部做完,才能執(zhí)行下一步,這時(shí)候就需要用到 WaitGroup
才能做到。
(推薦課程:Go教程)
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
i := 0
wg.Add(3) //task count wait to do
go func() {
defer wg.Done() // finish task1
fmt.Println("goroutine 1 done")
i++
}()
go func() {
defer wg.Done() // finish task2
fmt.Println("goroutine 2 done")
i++
}()
go func() {
defer wg.Done() // finish task3
fmt.Println("goroutine 3 done")
i++
}()
wg.Wait() // wait for tasks to be done
fmt.Println("all goroutine done")
fmt.Println(i)
}
Channel
另外一種實(shí)際的案例就是,我們需要主動(dòng)通知一個(gè) Goroutine
進(jìn)行停止的動(dòng)作。換句話說(shuō),當(dāng) App 啟動(dòng)時(shí),會(huì)在后臺(tái)跑一些監(jiān)控程序,而當(dāng)整個(gè) App 需要停止前,需要發(fā)個(gè) Notification
給后臺(tái)的監(jiān)控程序,將其先停止,這時(shí)候就需要用到 Channel
來(lái)通知??聪孪旅孢@個(gè)例子:
package main
import (
"fmt"
"time"
)
func main() {
exit := make(chan bool)
go func() {
for {
select {
case <-exit:
fmt.Println("Exit")
return
case <-time.After(2 * time.Second):
fmt.Println("Monitoring")
}
}
}()
time.Sleep(5 * time.Second)
fmt.Println("Notify Exit")
exit <- true //keep main goroutine alive
time.Sleep(5 * time.Second)
}
上面的例子可以發(fā)現(xiàn),用了一個(gè) Gogourtine
和 Channel
來(lái)控制。可以想像當(dāng)后臺(tái)有無(wú)數(shù)個(gè) Goroutine
的時(shí)候,我們就需要用多個(gè) Channel
才能進(jìn)行控制,也許 Goroutine
內(nèi)又會(huì)產(chǎn)生 Goroutine
,開發(fā)者這時(shí)候就會(huì)發(fā)現(xiàn)已經(jīng)無(wú)法單純使用 Channel
來(lái)控制多個(gè) Goroutine
了。這時(shí)候解決方式會(huì)是傳遞 Context
。
(推薦課程:Go Web編程)
Context
大家可以想像,今天有一個(gè)后臺(tái)任務(wù) A,A 任務(wù)又產(chǎn)生了 B 任務(wù),B 任務(wù)又產(chǎn)生了 C 任務(wù),也就是可以按照此模式一直產(chǎn)生下去,假設(shè)中途我們需要停止 A 任務(wù),而 A 又必須告訴 B 及 C 要一起停止,這時(shí)候通過(guò) context
方式是最快的了。
package main
import (
"context"
"fmt"
"time"
)
func foo(ctx context.Context, name string) {
go bar(ctx, name) // A calls B
for {
select {
case <-ctx.Done():
fmt.Println(name, "A Exit")
return
case <-time.After(1 * time.Second):
fmt.Println(name, "A do something")
}
}
}
func bar(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Println(name, "B Exit")
return
case <-time.After(2 * time.Second):
fmt.Println(name, "B do something")
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
go foo(ctx, "FooBar")
fmt.Println("client release connection, need to notify A, B exit")
time.Sleep(5 * time.Second)
cancel() //mock client exit, and pass the signal, ctx.Done() gets the signal time.Sleep(3 * time.Second)
time.Sleep(3 * time.Second)
}
package main
import (
"context"
"fmt"
"time"
)
func foo(ctx context.Context, name string) {
go bar(ctx, name) // A calls B
for {
select {
case <-ctx.Done():
fmt.Println(name, "A Exit")
return
case <-time.After(1 * time.Second):
fmt.Println(name, "A do something")
}
}
}
func bar(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Println(name, "B Exit")
return
case <-time.After(2 * time.Second):
fmt.Println(name, "B do something")
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
go foo(ctx, "FooBar")
fmt.Println("client release connection, need to notify A, B exit")
time.Sleep(5 * time.Second)
cancel() //mock client exit, and pass the signal, ctx.Done() gets the signal time.Sleep(3 * time.Second)
time.Sleep(3 * time.Second)
}
大家可以把 context
想成是一個(gè) controller
,可以隨時(shí)控制不確定個(gè)數(shù)的 Goroutine
,由上往下,只要宣告context.WithCancel
后,再任意時(shí)間點(diǎn)都可以通過(guò)cancel()
來(lái)停止整個(gè)后臺(tái)服務(wù)。實(shí)際案例會(huì)用在當(dāng) App 需要重新啟動(dòng)時(shí),要先通知全部 goroutine
停止,正常停止后,才會(huì)重新啟動(dòng) App。
(推薦微課:Go微課)
總結(jié)
根據(jù)不同的情境跟狀況來(lái)選擇不同的方式,做一個(gè)總結(jié):
- WaitGroup:需要將單一個(gè)工作分解成多個(gè)子任務(wù),等到全部完成后,才能進(jìn)行下一步,這時(shí)候用
WaitGroup
最適合了 - Channel + Select:
Channel
只能用在比較單純的Goroutine
情況下,如果要管理多個(gè)Goroutine
,建議還是 走context
會(huì)比較適合 - Context:如果您想一次控制全部的
Goroutine
,相信用context
會(huì)是最適合不過(guò)的,當(dāng)然context
不只有這特性,詳細(xì)可以參考『用 10 分鐘了解 Go 語(yǔ)言 context package 使用場(chǎng)景及介紹』
以上就是關(guān)于Go 語(yǔ)言中管理 Concurrency
的三種方式的相關(guān)介紹了,希望對(duì)大家有所幫助。