0 喜欢

在Go 语言内管理Concurrency 的三种方式

admin
admin
2023-09-01 16:55:21 阅读 971

原文地址

相信大家踏入Go 语言的世界,肯定是被强大的Concurrency 所吸引,Go 语言用最简单的关键字go就可以将任务丢到背景处理,但是怎么有效率的控制Concurrency,这是入门Go 语言必学的项目,本篇会介绍三种方式来带大家认识Concurrency,而这三种方式分别对应到三个不同的名词: WaitGroup , Channel , 及Context,底下用简单的范例带大家了解。

WaitGroup

先来了解有什么情境需要使用到WaitGroup,假设您有两台机器需要同时上传最新的程式码,两台机器分别上传完成后,才能执行最后的重启步骤。就像是把一个Job 同时拆成好几份同时一起做,可以减少不少时间,但是最后需要等到全部做完,才能执行下一步,这时候就需要用到WaitGroup 函式才能做到。底下看个简单例子

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

另外一种实际的案例就是,我们需要主动通知一个Goroutine 进行停止的动作。举例来说,当App 启动时,会在背景跑一些监控程式,而当整个App 需要停止前,需要发个Notification 给背景的监控程式,将其先停止,这时候就需要用到Channel 来通知。底下来看个范例:

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) }

Context

大家可以想像,今天有一个背景任务A,A 任务又产生了B 任务,B 任务又产生了C 任务,也就是可以按照此模式一直产生下去,假设中途我们需要停止A 任务,而A 又必须告诉B 及C 要一起停止,这时候透过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) }

大家可以把context 想成是一个controller,可以随时控制不确定个数的Goroutine,由上往下,只要宣告 后,context.WithCancel再任意时间点都可以透过cancel()来停止整个背景服务。这边实在案例会用在当App 需要重新restart 时,要先通知全部goroutine 停止,正常停止后,才会重新启动App。

总结

根据不同的情境跟状况来选择不同的函式,底下做个总结

  • WaitGroup: 需要将单一个Job 拆成多个子任务,等到全部完成后,才能进行下一步,这时候用WaitGroup 最适合了
  • Channel+select: Channel 只能用在比较单纯的Goroutine 状况下,如果要管理多个Goroutine,建议还是走context 会比较适合
  • Context: 如果你想一次控制全部的Goroutine,相信用context 会是最适合不过的,这也是现在Go 用最凶的地方,当然context 不只有这特性,详细可以参考『用10 分钟了解Go 语言context package使用场景及介绍

关于作者
admin
admin
admin@ifront.net
 获得点赞 173
 文章阅读量 173724
文章标签