Golang模式:一次性终止多个goroutine

huangapple go评论108阅读模式
英文:

Golang pattern to kill multiple goroutines at once

问题

我有两个goroutine,如下所示。我想要同步它们,以便当一个goroutine返回时,另一个goroutine也应该退出。在Go语言中,最好的方法是什么?

  1. func main() {
  2. go func() {
  3. ...
  4. if err != nil {
  5. return
  6. }
  7. }()
  8. go func() {
  9. ...
  10. if err != nil {
  11. return
  12. }
  13. }()
  14. }

我在这里模拟了这种情况https://play.golang.org/p/IqawStXt7rt,并尝试使用通道来信号化一个goroutine已完成。这看起来可能会导致对关闭的通道进行写入,从而导致恐慌。解决这个问题的最佳方法是什么?

英文:

I have two goroutines as shown in the snippet below. I want to synchronize them such that when one returns, the other one should also exit. What is best way in go to achieve this?

  1. func main() {
  2. go func() {
  3. ...
  4. if err != nil {
  5. return
  6. }
  7. }()
  8. go func() {
  9. ...
  10. if err != nil {
  11. return
  12. }
  13. }()
  14. }

I have simulated this scenario here https://play.golang.org/p/IqawStXt7rt and tried to solve it with a channel to signal a routine is done. This looks like there can be a write to closed channel resulting in a panic. What is the best way to solve this problem?

答案1

得分: 22

你可以使用上下文(context)在两个Go协程之间进行通信。

例如,

  1. package main
  2. import (
  3. "context"
  4. "sync"
  5. )
  6. func main() {
  7. ctx, cancel := context.WithCancel(context.Background())
  8. wg := sync.WaitGroup{}
  9. wg.Add(3)
  10. go func() {
  11. defer wg.Done()
  12. for {
  13. select {
  14. // 来自其他Go协程的消息完成
  15. case <-ctx.Done():
  16. // 结束
  17. }
  18. }
  19. }()
  20. go func() {
  21. defer wg.Done()
  22. for {
  23. select {
  24. // 来自其他Go协程的消息完成
  25. case <-ctx.Done():
  26. // 结束
  27. }
  28. }
  29. }()
  30. go func() {
  31. defer wg.Done()
  32. // 进行你的操作
  33. // 当这个Go协程结束时调用cancel
  34. cancel()
  35. }()
  36. wg.Wait()
  37. }
英文:

You can use context for communication between two go routines.
For example,

  1. package main
  2. import (
  3. &quot;context&quot;
  4. &quot;sync&quot;
  5. )
  6. func main() {
  7. ctx, cancel := context.WithCancel(context.Background())
  8. wg := sync.WaitGroup{}
  9. wg.Add(3)
  10. go func() {
  11. defer wg.Done()
  12. for {
  13. select {
  14. // msg from other goroutine finish
  15. case &lt;-ctx.Done():
  16. // end
  17. }
  18. }
  19. }()
  20. go func() {
  21. defer wg.Done()
  22. for {
  23. select {
  24. // msg from other goroutine finish
  25. case &lt;-ctx.Done():
  26. // end
  27. }
  28. }
  29. }()
  30. go func() {
  31. defer wg.Done()
  32. // your operation
  33. // call cancel when this goroutine ends
  34. cancel()
  35. }()
  36. wg.Wait()
  37. }

答案2

得分: 2

使用close在通道上发出完成信号。这允许多个goroutine通过在通道上接收来检查完成状态。

使用每个goroutine一个通道来表示goroutine的完成。

  1. done1 := make(chan struct{}) // 当goroutine 1返回时关闭
  2. done2 := make(chan struct{}) // 当goroutine 2返回时关闭
  3. go func() {
  4. defer close(done1)
  5. timer1 := time.NewTicker(1 * time.Second)
  6. defer timer1.Stop()
  7. timer2 := time.NewTicker(2 * time.Second)
  8. defer timer2.Stop()
  9. for {
  10. select {
  11. case <-done2:
  12. // 另一个goroutine已返回。
  13. fmt.Println("done func 1")
  14. return
  15. case <-timer1.C:
  16. fmt.Println("timer1 func 1")
  17. case <-timer2.C:
  18. fmt.Println("timer2 func 1")
  19. return
  20. }
  21. }
  22. }()
  23. go func() {
  24. defer close(done2)
  25. for {
  26. select {
  27. case <-done1:
  28. // 另一个goroutine已返回。
  29. fmt.Println("done func 2")
  30. return
  31. default:
  32. time.Sleep(3 * time.Second)
  33. fmt.Println("sleep done from func 2")
  34. return
  35. }
  36. }
  37. }()
  38. fmt.Println("等待goroutine完成")
  39. // 等待两个goroutine返回。这里等待的顺序无关紧要。
  40. <-done1
  41. <-done2
  42. fmt.Println("全部完成")

在playground上运行

英文:

Use close on a channel to signal completion. This allows multiple goroutines to check for completion by receiving on the channel.

Use one channel per goroutine to signal completion of the goroutine.

  1. done1 := make(chan struct{}) // closed when goroutine 1 returns
  2. done2 := make(chan struct{}) // closed when goroutine 2 returns
  3. go func() {
  4. defer close(done1)
  5. timer1 := time.NewTicker(1 * time.Second)
  6. defer timer1.Stop()
  7. timer2 := time.NewTicker(2 * time.Second)
  8. defer timer2.Stop()
  9. for {
  10. select {
  11. case &lt;-done2:
  12. // The other goroutine returned.
  13. fmt.Println(&quot;done func 1&quot;)
  14. return
  15. case &lt;-timer1.C:
  16. fmt.Println(&quot;timer1 func 1&quot;)
  17. case &lt;-timer2.C:
  18. fmt.Println(&quot;timer2 func 1&quot;)
  19. return
  20. }
  21. }
  22. }()
  23. go func() {
  24. defer close(done2)
  25. for {
  26. select {
  27. case &lt;-done1:
  28. // The other goroutine returned.
  29. fmt.Println(&quot;done func 2&quot;)
  30. return
  31. default:
  32. time.Sleep(3 * time.Second)
  33. fmt.Println(&quot;sleep done from func 2&quot;)
  34. return
  35. }
  36. }
  37. }()
  38. fmt.Println(&quot;waiting for goroutines to complete&quot;)
  39. // Wait for both goroutines to return. The order that
  40. // we wait here does not matter.
  41. &lt;-done1
  42. &lt;-done2
  43. fmt.Println(&quot;all done&quot;)

Run it on the playground.

答案3

得分: 2

首先,将等待goroutine和done通道分开。

使用sync.WaitGroup来协调goroutine。

  1. func main() {
  2. wait := &sync.WaitGroup{}
  3. N := 3
  4. wait.Add(N)
  5. for i := 1; i <= N; i++ {
  6. go goFunc(wait, i, true)
  7. }
  8. wait.Wait()
  9. fmt.Println(`Exiting main`)
  10. }

每个goroutine的代码如下:

  1. // 实际goroutine的代码
  2. func goFunc(wait *sync.WaitGroup, i int, closer bool) {
  3. defer wait.Done()
  4. defer fmt.Println(`Exiting `, i)
  5. T := time.Tick(time.Duration(100*i) * time.Millisecond)
  6. for {
  7. select {
  8. case <-T:
  9. fmt.Println(`Tick `, i)
  10. if closer {
  11. return
  12. }
  13. }
  14. }
  15. }

(https://play.golang.org/p/mDO4P56lzBU)

我们的main函数成功地在退出之前等待goroutine退出。每个goroutine都在关闭自身,我们希望有一种方法可以同时取消所有的goroutine。

我们将使用一个chan来实现这个,并利用通道接收的特性:

引用:关闭的通道上的接收操作总是可以立即进行,之前发送的任何值都已经被接收到后,会产生元素类型的零值。
(https://golang.org/ref/spec#Receive_operator)

我们修改我们的goroutine来检查CLOSE:

  1. func goFunc(wait *sync.WaitGroup, i int, closer bool, CLOSE chan struct{}) {
  2. defer wait.Done()
  3. defer fmt.Println(`Exiting `, i)
  4. T := time.Tick(time.Duration(100*i) * time.Millisecond)
  5. for {
  6. select {
  7. case <-CLOSE:
  8. return
  9. case <-T:
  10. fmt.Println(`Tick `, i)
  11. if closer {
  12. close(CLOSE)
  13. }
  14. }
  15. }
  16. }

然后我们改变我们的func main,使其传递CLOSE通道,并设置closer变量,以便只有最后一个goroutine会触发关闭:

  1. func main() {
  2. wait := &sync.WaitGroup{}
  3. N := 3
  4. CLOSE := make(chan struct{})
  5. // 启动goroutine
  6. wait.Add(N)
  7. for i := 1; i <= N; i++ {
  8. go goFunc(wait, i, i == N, CLOSE)
  9. }
  10. // 等待goroutine完成
  11. wait.Wait()
  12. fmt.Println(`Exiting main`)
  13. }

(https://play.golang.org/p/E91CtRAHDp2)

现在看起来一切都正常工作。

但实际上并不是这样的。并发是困难的。这段代码中潜伏着一个错误,只等着在生产环境中咬你一口。让我们找出它。

将我们的示例更改为每个goroutine都会关闭:

  1. func main() {
  2. wait := &sync.WaitGroup{}
  3. N := 3
  4. CLOSE := make(chan struct{})
  5. // 启动goroutine
  6. wait.Add(N)
  7. for i := 1; i <= N; i++ {
  8. go goFunc(wait, i, true /*** 每个goroutine都会关闭 ***/, CLOSE)
  9. }
  10. // 等待goroutine完成
  11. wait.Wait()
  12. fmt.Println(`Exiting main`)
  13. }

将goroutine更改为在关闭之前等待一段时间。我们希望两个goroutine在同一时间关闭:

  1. // 实际goroutine的代码
  2. func goFunc(wait *sync.WaitGroup, i int, closer bool, CLOSE chan struct{}) {
  3. defer wait.Done()
  4. defer fmt.Println(`Exiting `, i)
  5. T := time.Tick(time.Duration(100*i) * time.Millisecond)
  6. for {
  7. select {
  8. case <-CLOSE:
  9. return
  10. case <-T:
  11. fmt.Println(`Tick `, i)
  12. if closer {
  13. /*** 在关闭之前等待一段时间 ***/
  14. time.Sleep(time.Second)
  15. close(CLOSE)
  16. }
  17. }
  18. }
  19. }

(https://play.golang.org/p/YHnbDpnJCks)

我们遇到了崩溃:

  1. Tick 1
  2. Tick 2
  3. Tick 3
  4. Exiting 1
  5. Exiting 2
  6. panic: close of closed channel
  7. goroutine 7 [running]:
  8. main.goFunc(0x40e020, 0x2, 0x68601, 0x430080)
  9. /tmp/sandbox558886627/prog.go:24 +0x2e0
  10. created by main.main
  11. /tmp/sandbox558886627/prog.go:38 +0xc0
  12. Program exited: status 2.

虽然在关闭的通道上接收会立即返回,但是你不能关闭一个已经关闭的通道。

我们需要一些协调。我们可以使用sync.Mutex和一个bool来指示我们是否已经关闭了通道。让我们创建一个结构体来实现这个:

  1. type Close struct {
  2. C chan struct{}
  3. l sync.Mutex
  4. closed bool
  5. }
  6. func NewClose() *Close {
  7. return &Close {
  8. C: make(chan struct{}),
  9. }
  10. }
  11. func (c *Close) Close() {
  12. c.l.Lock()
  13. if (!c.closed) {
  14. c.closed=true
  15. close(c.C)
  16. }
  17. c.l.Unlock()
  18. }

重写我们的gofunc和main函数,使用我们的新Close结构体,然后我们就可以运行了:
https://play.golang.org/p/eH3djHu8EXW

并发的问题在于你总是需要考虑如果另一个“线程”在代码的任何其他地方会发生什么。

英文:

First separate the waiting on go-routines and the done channel.

Use a sync.WaitGroup to coordinate the goroutines.

  1. func main() {
  2. wait := &amp;sync.WaitGroup{}
  3. N := 3
  4. wait.Add(N)
  5. for i := 1; i &lt;= N; i++ {
  6. go goFunc(wait, i, true)
  7. }
  8. wait.Wait()
  9. fmt.Println(`Exiting main`)
  10. }

Each goroutine will look like this:

  1. // code for the actual goroutine
  2. func goFunc(wait *sync.WaitGroup, i int, closer bool) {
  3. defer wait.Done()
  4. defer fmt.Println(`Exiting `, i)
  5. T := time.Tick(time.Duration(100*i) * time.Millisecond)
  6. for {
  7. select {
  8. case &lt;-T:
  9. fmt.Println(`Tick `, i)
  10. if closer {
  11. return
  12. }
  13. }
  14. }
  15. }

(https://play.golang.org/p/mDO4P56lzBU)

Our main func is successfully waiting for the goroutines to exit before it exits. Each goroutine is closing itself, and we want a way to cancel all our goroutines at the same time.

We'll do this with a chan, and make use of this feature of receiving from channels:

QUOTE: A receive operation on a closed channel can always proceed immediately, yielding the element type's zero value after any previously sent values have been received.
(https://golang.org/ref/spec#Receive_operator)

We modify our goroutines to check for a CLOSE:

  1. func goFunc(wait *sync.WaitGroup, i int, closer bool, CLOSE chan struct{}) {
  2. defer wait.Done()
  3. defer fmt.Println(`Exiting `, i)
  4. T := time.Tick(time.Duration(100*i) * time.Millisecond)
  5. for {
  6. select {
  7. case &lt;-CLOSE:
  8. return
  9. case &lt;-T:
  10. fmt.Println(`Tick `, i)
  11. if closer {
  12. close(CLOSE)
  13. }
  14. }
  15. }
  16. }

and then we change our func main so that it passes the CLOSE channel through, and we'll set the closer variable so that only the last of our goroutines will trigger the close:

  1. func main() {
  2. wait := &amp;sync.WaitGroup{}
  3. N := 3
  4. CLOSE := make(chan struct{})
  5. // Launch the goroutines
  6. wait.Add(N)
  7. for i := 1; i &lt;= N; i++ {
  8. go goFunc(wait, i, i == N, CLOSE)
  9. }
  10. // Wait for the goroutines to finish
  11. wait.Wait()
  12. fmt.Println(`Exiting main`)
  13. }

(https://play.golang.org/p/E91CtRAHDp2)

Now it looks like everything is working.

But it isn't. Concurrency is hard. There's a bug lurking in this code, just waiting to bite you in production. Let's surface it.

Change our example so that every goroutine will close:

  1. func main() {
  2. wait := &amp;sync.WaitGroup{}
  3. N := 3
  4. CLOSE := make(chan struct{})
  5. // Launch the goroutines
  6. wait.Add(N)
  7. for i := 1; i &lt;= N; i++ {
  8. go goFunc(wait, i, true /*** EVERY GOROUTINE WILL CLOSE ***/, CLOSE)
  9. }
  10. // Wait for the goroutines to finish
  11. wait.Wait()
  12. fmt.Println(`Exiting main`)
  13. }

Change goroutine so that it takes a while before closing. We want two goroutines to be about to close at the same time:

  1. // code for the actual goroutine
  2. func goFunc(wait *sync.WaitGroup, i int, closer bool, CLOSE chan struct{}) {
  3. defer wait.Done()
  4. defer fmt.Println(`Exiting `, i)
  5. T := time.Tick(time.Duration(100*i) * time.Millisecond)
  6. for {
  7. select {
  8. case &lt;-CLOSE:
  9. return
  10. case &lt;-T:
  11. fmt.Println(`Tick `, i)
  12. if closer {
  13. /*** TAKE A WHILE BEFORE CLOSING ***/
  14. time.Sleep(time.Second)
  15. close(CLOSE)
  16. }
  17. }
  18. }
  19. }

(https://play.golang.org/p/YHnbDpnJCks)

We crash with:

  1. Tick 1
  2. Tick 2
  3. Tick 3
  4. Exiting 1
  5. Exiting 2
  6. panic: close of closed channel
  7. goroutine 7 [running]:
  8. main.goFunc(0x40e020, 0x2, 0x68601, 0x430080)
  9. /tmp/sandbox558886627/prog.go:24 +0x2e0
  10. created by main.main
  11. /tmp/sandbox558886627/prog.go:38 +0xc0
  12. Program exited: status 2.

While a receive on a closed channel returns immediately, you cannot close a closed channel.

We need a little coordination. We can do this with a sync.Mutex and a bool to indicate whether we've closed the channel or not. Let's create a struct to do this:

  1. type Close struct {
  2. C chan struct{}
  3. l sync.Mutex
  4. closed bool
  5. }
  6. func NewClose() *Close {
  7. return &amp;Close {
  8. C: make(chan struct{}),
  9. }
  10. }
  11. func (c *Close) Close() {
  12. c.l.Lock()
  13. if (!c.closed) {
  14. c.closed=true
  15. close(c.C)
  16. }
  17. c.l.Unlock()
  18. }

Rewrite our gofunc and our main to use our new Close struct, and we're good to go:
https://play.golang.org/p/eH3djHu8EXW

The problem with concurrency is that you always need to be wondering what would happen if another 'thread' was anywhere else in the code.

答案4

得分: 1

你的问题是你希望在"完成"通道上发送一次,但却被多个监听器接收。你还需要考虑发送到done通道的是由goroutine接收还是由main函数接收。

我建议你将等待goroutine和done通道分开。

  1. import `sync`
  2. // 这段代码将在两个函数完成之前等待,然后结束
  3. func main {
  4. var wait sync.WaitGroup
  5. wait.Add(2)
  6. go func() {
  7. defer wait.Done()
  8. }()
  9. go g() {
  10. defer wait.Done()
  11. }()
  12. wait.Wait()
  13. }

现在,如何管理Done。解决方案是使用sync.Cond,并让每个goroutine运行自己的goroutine来等待Cond。以下是一个示例:

  1. package main
  2. import (
  3. `fmt`
  4. `sync`
  5. `time`
  6. )
  7. // WaitForIt 封装了一个Cond和一个Mutex,提供了一个更简单的API:
  8. // .WAIT() chan struct{} 返回一个通道,当WaitForIt完成时会发出信号。
  9. // .Done() 表示WaitForIt完成。
  10. type WaitForIt struct {
  11. L *sync.Mutex
  12. Cond *sync.Cond
  13. }
  14. func NewWaitForIt() *WaitForIt {
  15. l := &amp;sync.Mutex{}
  16. c := sync.NewCond(l)
  17. return &amp;WaitForIt{ l, c }
  18. }
  19. // WAIT 返回一个通道,当Cond触发时会发出信号。
  20. func (w *WaitForIt) WAIT() chan struct{} {
  21. D := make(chan struct{})
  22. go func() {
  23. w.L.Lock()
  24. defer w.L.Unlock()
  25. w.Cond.Wait()
  26. D &lt;- struct{}{}
  27. close(D)
  28. }()
  29. return D
  30. }
  31. // Done 表示Cond应该被触发。
  32. func (w *WaitForIt) Done() {
  33. w.Cond.Broadcast()
  34. }
  35. // doneFunc 使用一个通道来启动函数f,当函数应该停止时,通道会发出信号。
  36. // 它还处理WaitGroup的同步。
  37. func doneFunc(wait *sync.WaitGroup, waitForIt *WaitForIt, f func(DONE chan struct{})) {
  38. defer wait.Done()
  39. f(waitForIt.WAIT())
  40. }
  41. func main() {
  42. // wait 将在main()函数级别协调所有的goroutine
  43. // waitForIt 将协调所有的goroutine
  44. wait := &amp;sync.WaitGroup{}
  45. // waitForIt 用于指示goroutine何时关闭
  46. waitForIt := NewWaitForIt()
  47. // goFunc 生成每个goroutine。只有3秒的goroutine会关闭所有的goroutine
  48. goFunc := func(seconds int) func(chan struct{}) {
  49. return func(DONE chan struct{}) {
  50. // 这是每个goroutine的实际代码
  51. // 它创建一个计时器,持续一定的秒数,
  52. // 在计时器结束后打印秒数,
  53. // 或者在DONE被触发时退出
  54. timer := time.NewTicker(time.Duration(seconds) * time.Second)
  55. defer timer.Stop()
  56. for {
  57. select {
  58. case &lt;- DONE:
  59. return
  60. case &lt;- timer.C:
  61. if (3==seconds) {
  62. waitForIt.Done()
  63. // 不要在这里关闭 - 我们将在DONE被触发时关闭
  64. }
  65. }
  66. }
  67. }
  68. }
  69. // 启动3个goroutine,每个都在等待关闭信号
  70. for i:=1; i&lt;=3; i++ {
  71. wait.Add(1)
  72. go doneFunc(wait, waitForIt, goFunc(i))
  73. }
  74. // 等待所有的goroutine完成,然后我们完成了
  75. wait.Wait()
  76. }

这是使用WaitForIt实现的示例:https://play.golang.org/p/llphW73G1xE
请注意,我不得不在WaitForIt.Done中删除Lock()调用。尽管文档说你可以持有锁,但它会阻塞第二个goroutine的完成。

英文:

Your problem is that you want a single send on the DONE channel to be received by multiple listeners. You also need to consider whether a send on the done channel is received by your goroutines, or by your main func.

I suggest you rather separate the waiting on go-routines and the done channel.

  1. import `sync`
  2. // This code will wait for the two functions to complete before ending
  3. func main {
  4. var wait sync.WaitGroup
  5. wait.Add(2)
  6. go func() {
  7. defer wait.Done()
  8. }()
  9. go g() {
  10. defer wait.Done()
  11. }()
  12. wait.Wait()
  13. }

Now, how to manage the Done. Well, the solution is to use a sync.Cond and have each goroutine run its own goroutine to wait on the Cond. Here's an example:

  1. package main
  2. import (
  3. `fmt`
  4. `sync`
  5. `time`
  6. )
  7. // WaitForIt wraps a Cond and a Mutex for a simpler API:
  8. // .WAIT() chan struct{} will return a channel that will be
  9. // signalled when the WaitForIt is done.
  10. // .Done() will indicate that the WaitForIt is done.
  11. type WaitForIt struct {
  12. L *sync.Mutex
  13. Cond *sync.Cond
  14. }
  15. func NewWaitForIt() *WaitForIt {
  16. l := &amp;sync.Mutex{}
  17. c := sync.NewCond(l)
  18. return &amp;WaitForIt{ l, c }
  19. }
  20. // WAIT returns a chan that will be signalled when
  21. // the Cond is triggered.
  22. func (w *WaitForIt) WAIT() chan struct{} {
  23. D := make(chan struct{})
  24. go func() {
  25. w.L.Lock()
  26. defer w.L.Unlock()
  27. w.Cond.Wait()
  28. D &lt;- struct{}{}
  29. close(D)
  30. }()
  31. return D
  32. }
  33. // Done indicates that the Cond should be triggered.
  34. func (w *WaitForIt) Done() {
  35. w.Cond.Broadcast()
  36. }
  37. // doneFunc launches the func f with a chan that will be signalled when the
  38. // func should stop. It also handles WaitGroup synchronization
  39. func doneFunc(wait *sync.WaitGroup, waitForIt *WaitForIt, f func(DONE chan struct{})) {
  40. defer wait.Done()
  41. f(waitForIt.WAIT())
  42. }
  43. func main() {
  44. // wait will coordinate all the goroutines at the level of main()
  45. // between themselves the waitForIt will do the coordination
  46. wait := &amp;sync.WaitGroup{}
  47. // waitForIt indicates to the goroutines when they should shut
  48. waitForIt := NewWaitForIt()
  49. // goFunc generates each goroutine. Only the 3-second goroutine will
  50. // shutdown all goroutines
  51. goFunc := func(seconds int) func(chan struct{}) {
  52. return func(DONE chan struct{}) {
  53. // this is the actual code of each goroutine
  54. // it makes a ticker for a number of seconds,
  55. // and prints the seconds after the ticker elapses,
  56. // or exits if DONE is triggered
  57. timer := time.NewTicker(time.Duration(seconds) * time.Second)
  58. defer timer.Stop()
  59. for {
  60. select {
  61. case &lt;- DONE:
  62. return
  63. case &lt;- timer.C:
  64. if (3==seconds) {
  65. waitForIt.Done()
  66. // Don&#39;t shutdown here - we&#39;ll shutdown
  67. // when our DONE is signalled
  68. }
  69. }
  70. }
  71. }
  72. }
  73. // launch 3 goroutines, each waiting on a shutdown signal
  74. for i:=1; i&lt;=3; i++ {
  75. wait.Add(1)
  76. go doneFunc(wait, waitForIt, goFunc(i))
  77. }
  78. // wait for all the goroutines to complete, and we&#39;re done
  79. wait.Wait()
  80. }

Here's your example implemented using WaitForIt: https://play.golang.org/p/llphW73G1xE
Note that I had to remove the Lock() call in WaitForIt.Done. Although the documentation says you're allowed to hold the lock, it was blocking your 2nd goroutine from completing.

答案5

得分: 0

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. "time"
  6. )
  7. func func1(done chan struct{}, wg *sync.WaitGroup) {
  8. defer wg.Done()
  9. timer1 := time.NewTicker(1 * time.Second)
  10. timer2 := time.NewTicker(2 * time.Second)
  11. for {
  12. select {
  13. case <-timer1.C:
  14. fmt.Println("定时器1函数1")
  15. case <-timer2.C:
  16. // 请求GC清理定时器timer1和timer2
  17. // 因为goroutine即将返回
  18. timer1.Stop()
  19. timer2.Stop()
  20. fmt.Println("定时器2函数1")
  21. done <- struct{}{} // 通知另一个goroutine终止
  22. fmt.Println("从函数1发送done")
  23. return
  24. case <-done:
  25. // 请求GC清理定时器timer1和timer2
  26. // 因为goroutine即将返回
  27. timer1.Stop()
  28. timer2.Stop()
  29. fmt.Println("函数1完成")
  30. return
  31. }
  32. }
  33. }
  34. func func2(done chan struct{}, wg *sync.WaitGroup) {
  35. defer wg.Done()
  36. timer3 := time.NewTicker(3 * time.Second)
  37. for {
  38. select {
  39. case <-timer3.C:
  40. // 请求GC清理定时器timer3
  41. // 因为goroutine即将返回
  42. timer3.Stop()
  43. fmt.Println("定时器3函数2")
  44. done <- struct{}{} // 通知另一个goroutine终止
  45. fmt.Println("从函数2发送done")
  46. return
  47. case <-done:
  48. // 请求GC清理定时器timer3
  49. // 因为goroutine即将返回
  50. timer3.Stop()
  51. fmt.Println("函数2完成")
  52. return
  53. }
  54. }
  55. }
  56. func main() {
  57. // 用于goroutine之间的信号通信的通道
  58. done := make(chan struct{})
  59. // WaitGroup
  60. wg := sync.WaitGroup{}
  61. wg.Add(2)
  62. // 启动func1的goroutine
  63. go func1(done, &wg)
  64. // 启动func2的goroutine
  65. go func2(done, &wg)
  66. fmt.Println("开始休眠")
  67. // 等待goroutine完成
  68. wg.Wait()
  69. // 等待15秒钟
  70. // 如果不需要,请删除下面的代码
  71. time.Sleep(15 * time.Second)
  72. fmt.Println("等待了15秒钟")
  73. }
英文:
  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;sync&quot;
  5. &quot;time&quot;
  6. )
  7. func func1(done chan struct{}, wg *sync.WaitGroup) {
  8. defer wg.Done()
  9. timer1 := time.NewTicker(1 * time.Second)
  10. timer2 := time.NewTicker(2 * time.Second)
  11. for {
  12. select {
  13. case &lt;-timer1.C:
  14. fmt.Println(&quot;timer1 func 1&quot;)
  15. case &lt;-timer2.C:
  16. // Ask GC to sweep the tickers timer1, timer2
  17. // as goroutine should return
  18. timer1.Stop()
  19. timer2.Stop()
  20. fmt.Println(&quot;timer2 func 1&quot;)
  21. done &lt;- struct{}{} // Signal the other goroutine to terminate
  22. fmt.Println(&quot;sent done from func 1&quot;)
  23. return
  24. case &lt;-done:
  25. // Ask GC to sweep the tickers timer1, timer2
  26. // as goroutine should return
  27. timer1.Stop()
  28. timer2.Stop()
  29. fmt.Println(&quot;done func 1&quot;)
  30. return
  31. }
  32. }
  33. }
  34. func func2(done chan struct{}, wg *sync.WaitGroup) {
  35. defer wg.Done()
  36. timer3 := time.NewTicker(3 * time.Second)
  37. for {
  38. select {
  39. case &lt;-timer3.C:
  40. // Ask GC to sweep the tickers timer3
  41. // as goroutine should return
  42. timer3.Stop()
  43. fmt.Println(&quot;timer3 func 2&quot;)
  44. done &lt;- struct{}{} // Signal the other goroutine to terminate
  45. fmt.Println(&quot;sent done from func 2&quot;)
  46. return
  47. case &lt;-done:
  48. // Ask GC to sweep the tickers timer3
  49. // as goroutine should return
  50. timer3.Stop()
  51. fmt.Println(&quot;done func 2&quot;)
  52. return
  53. }
  54. }
  55. }
  56. func main() {
  57. // Chan used for signalling between goroutines
  58. done := make(chan struct{})
  59. // WaitGroup
  60. wg := sync.WaitGroup{}
  61. wg.Add(2)
  62. // Spawn the goroutine for func1
  63. go func1(done, &amp;wg)
  64. // Spawn the goroutine for func2
  65. go func2(done, &amp;wg)
  66. fmt.Println(&quot;starting sleep&quot;)
  67. // Wait for the goroutines
  68. wg.Wait()
  69. // Wait for 15 seconds
  70. // If not required, please remove
  71. // the lines below
  72. time.Sleep(15 * time.Second)
  73. fmt.Println(&quot;waited 15 seconds&quot;)
  74. }

答案6

得分: 0

你可以使用通道关闭模式在多个Go协程中等待。

  1. package main
  2. import (
  3. "os"
  4. "os/signal"
  5. "sync"
  6. "syscall"
  7. )
  8. type RunGroup struct {
  9. sync.WaitGroup
  10. }
  11. // Run处理等待组状态
  12. func (runGroup *RunGroup) Run(f func()) {
  13. runGroup.Add(1)
  14. go func() {
  15. f()
  16. runGroup.Done()
  17. }()
  18. }
  19. func doStuff(done <-chan any, id int) {
  20. println("Doing something", id)
  21. <-done
  22. println("DONE", id)
  23. }
  24. func main() {
  25. // Done通道
  26. done := make(chan any)
  27. // 设置关闭监听器
  28. sigChan := make(chan os.Signal, 1)
  29. signal.Notify(sigChan, syscall.SIGTERM)
  30. signal.Notify(sigChan, syscall.SIGINT)
  31. go func() {
  32. rawSig := <-sigChan
  33. sig := rawSig.String()
  34. println("Caught signal, shutting down.", sig)
  35. close(done)
  36. }()
  37. runGroup := RunGroup{}
  38. // 做一些事情
  39. runGroup.Run(func() {
  40. doStuff(done, 1)
  41. })
  42. runGroup.Run(func() {
  43. doStuff(done, 2)
  44. })
  45. runGroup.Run(func() {
  46. doStuff(done, 3)
  47. })
  48. // 主线程等待中断
  49. runGroup.Wait()
  50. }

输出

  1. go run ./main.go
  2. Doing something 3
  3. Doing something 2
  4. Doing something 1
  5. ^CCaught signal, shutting down. interrupt
  6. DONE 3
  7. DONE 1
  8. DONE 2
英文:

You could use channel closing pattern to wait within multiple go routines.

  1. package main
  2. import (
  3. &quot;os&quot;
  4. &quot;os/signal&quot;
  5. &quot;sync&quot;
  6. &quot;syscall&quot;
  7. )
  8. type RunGroup struct {
  9. sync.WaitGroup
  10. }
  11. // Run handles wait group state
  12. func (runGroup *RunGroup) Run(f func()) {
  13. runGroup.Add(1)
  14. go func() {
  15. f()
  16. runGroup.Done()
  17. }()
  18. }
  19. func doStuff(done &lt;-chan any, id int) {
  20. println(&quot;Doing something&quot;, id)
  21. &lt;-done
  22. println(&quot;DONE&quot;, id)
  23. }
  24. func main() {
  25. // Done channel
  26. done := make(chan any)
  27. // Setup Shutdown listeners
  28. sigChan := make(chan os.Signal, 1)
  29. signal.Notify(sigChan, syscall.SIGTERM)
  30. signal.Notify(sigChan, syscall.SIGINT)
  31. go func() {
  32. rawSig := &lt;-sigChan
  33. sig := rawSig.String()
  34. println(&quot;Caught signal, shutting down.&quot;, sig)
  35. close(done)
  36. }()
  37. runGroup := RunGroup{}
  38. // Do some stuff
  39. runGroup.Run(func () {
  40. doStuff(done, 1)
  41. })
  42. runGroup.Run(func () {
  43. doStuff(done, 2)
  44. })
  45. runGroup.Run(func () {
  46. doStuff(done, 3)
  47. })
  48. // Wait mainthread until interrupt
  49. runGroup.Wait()
  50. }

Output

  1. go run ./main.go
  2. Doing something 3
  3. Doing something 2
  4. Doing something 1
  5. ^CCaught signal, shutting down. interrupt
  6. DONE 3
  7. DONE 1
  8. DONE 2

huangapple
  • 本文由 发表于 2020年4月3日 15:57:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/61007385.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定