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

开发 前端
本章会介绍几种方式来带大家认识并发,而这三种方式分别对应到三个不同的名词:WaitGroup,Channel,及 Context。下面用简单的范例带大家了解。

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

[[337382]]

WaitGroup

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

  1. package main 
  2.  
  3. import ( 
  4.     "fmt" 
  5.     "sync" 
  6.  
  7. func main() { 
  8.     var wg sync.WaitGroup 
  9.     i := 0 
  10.     wg.Add(3) //task count wait to do 
  11.     go func() { 
  12.         defer wg.Done() // finish task1 
  13.         fmt.Println("goroutine 1 done"
  14.         i++ 
  15.     }() 
  16.     go func() { 
  17.         defer wg.Done() // finish task2 
  18.         fmt.Println("goroutine 2 done"
  19.         i++ 
  20.     }() 
  21.     go func() { 
  22.         defer wg.Done() // finish task3 
  23.         fmt.Println("goroutine 3 done"
  24.         i++ 
  25.     }() 
  26.     wg.Wait() // wait for tasks to be done 
  27.     fmt.Println("all goroutine done"
  28.     fmt.Println(i) 

Channel

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

  1. package main 
  2.  
  3. import ( 
  4.     "fmt" 
  5.     "time" 
  6.  
  7. func main() { 
  8.     exit := make(chan bool) 
  9.     go func() { 
  10.         for { 
  11.             select { 
  12.             case <-exit: 
  13.                 fmt.Println("Exit"
  14.                 return 
  15.             case <-time.After(2 * time.Second): 
  16.                 fmt.Println("Monitoring"
  17.             } 
  18.         } 
  19.     }() 
  20.     time.Sleep(5 * time.Second
  21.     fmt.Println("Notify Exit"
  22.     exit <- true //keep main goroutine alive 
  23.     time.Sleep(5 * time.Second

上面的例子可以发现,用了一个 Gogourtine 和 Channel 来控制。可以想像当后台有无数个 Goroutine 的时候,我们就需要用多个 Channel 才能进行控制,也许 Goroutine 内又会产生 Goroutine,开发者这时候就会发现已经无法单纯使用 Channel 来控制多个 Goroutine 了。这时候解决方式会是传递 Context。

Context

大家可以想像,今天有一个后台任务 A,A 任务又产生了 B 任务,B 任务又产生了 C 任务,也就是可以按照此模式一直产生下去,假设中途我们需要停止 A 任务,而 A 又必须告诉 B 及 C 要一起停止,这时候通过 context 方式是最快的了。

 

  1. package main 
  2.  
  3. import ( 
  4.     "context" 
  5.     "fmt" 
  6.     "time" 
  7.  
  8. func foo(ctx context.Context, name string) { 
  9.     go bar(ctx, name) // A calls B 
  10.     for { 
  11.         select { 
  12.         case <-ctx.Done(): 
  13.             fmt.Println(name"A Exit"
  14.             return 
  15.         case <-time.After(1 * time.Second): 
  16.             fmt.Println(name"A do something"
  17.         } 
  18.     } 
  19.  
  20. func bar(ctx context.Context, name string) { 
  21.     for { 
  22.         select { 
  23.         case <-ctx.Done(): 
  24.             fmt.Println(name"B Exit"
  25.             return 
  26.         case <-time.After(2 * time.Second): 
  27.             fmt.Println(name"B do something"
  28.         } 
  29.     } 
  30.  
  31. func main() { 
  32.     ctx, cancel := context.WithCancel(context.Background()) 
  33.     go foo(ctx, "FooBar"
  34.     fmt.Println("client release connection, need to notify A, B exit"
  35.     time.Sleep(5 * time.Second
  36.     cancel() //mock client exit, and pass the signal, ctx.Done() gets the signal  time.Sleep(3 * time.Second
  37.     time.Sleep(3 * time.Second
  38.  
  39. package main 
  40.  
  41. import ( 
  42.     "context" 
  43.     "fmt" 
  44.     "time" 
  45.  
  46. func foo(ctx context.Context, name string) { 
  47.     go bar(ctx, name) // A calls B 
  48.     for { 
  49.         select { 
  50.         case <-ctx.Done(): 
  51.             fmt.Println(name"A Exit"
  52.             return 
  53.         case <-time.After(1 * time.Second): 
  54.             fmt.Println(name"A do something"
  55.         } 
  56.     } 
  57.  
  58. func bar(ctx context.Context, name string) { 
  59.     for { 
  60.         select { 
  61.         case <-ctx.Done(): 
  62.             fmt.Println(name"B Exit"
  63.             return 
  64.         case <-time.After(2 * time.Second): 
  65.             fmt.Println(name"B do something"
  66.         } 
  67.     } 
  68.  
  69. func main() { 
  70.     ctx, cancel := context.WithCancel(context.Background()) 
  71.     go foo(ctx, "FooBar"
  72.     fmt.Println("client release connection, need to notify A, B exit"
  73.     time.Sleep(5 * time.Second
  74.     cancel() //mock client exit, and pass the signal, ctx.Done() gets the signal  time.Sleep(3 * time.Second
  75.     time.Sleep(3 * time.Second

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

总结

根据不同的情境跟状况来选择不同的方式,做一个总结:

  • WaitGroup:需要将单一个工作分解成多个子任务,等到全部完成后,才能进行下一步,这时候用 WaitGroup 最适合了
  • Channel + Select:Channel 只能用在比较单纯的 Goroutine 情况下,如果要管理多个 Goroutine,建议还是 走 context 会比较适合
  • Context:如果您想一次控制全部的 Goroutine,相信用 context 会是最适合不过的。

 

责任编辑:未丽燕 来源: 今日头条
相关推荐

2023-08-15 08:01:07

Go 语言排序

2022-05-31 16:00:46

Go 编程语言复制文件Go 标准库

2019-08-28 09:04:02

Go语言Python操作系统

2014-04-09 09:32:24

Go并发

2023-12-19 16:43:01

2014-12-31 17:42:47

LBSAndroid地图

2021-11-05 21:33:28

Redis数据高并发

2019-11-20 18:52:24

物联网智能照明智能恒温器

2021-06-24 08:52:19

单点登录代码前端

2012-07-17 09:16:16

SpringSSH

2023-03-28 07:46:46

go语言kubernetes

2019-02-27 07:45:25

物联网健康管理IOT

2022-06-26 06:31:25

Linux电子游戏

2021-07-15 23:18:48

Go语言并发

2024-04-07 11:33:02

Go逃逸分析

2011-07-25 12:41:38

接入方式布线

2017-07-14 15:07:23

2018-04-02 14:29:18

Java多线程方式

2010-09-13 12:19:03

2009-07-29 09:36:07

无线通信接入方式
点赞
收藏

51CTO技术栈公众号