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

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

Golang pattern to kill multiple goroutines at once

问题

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

func main() {

  go func() {
    ...
    if err != nil {
      return
    }
  }()

  go func() {
    ...
    if err != nil {
      return
    }
  }()


}

我在这里模拟了这种情况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?

func main() {

  go func() {
    ...
    if err != nil {
      return
    }
  }()

  go func() {
    ...
    if err != nil {
      return
    }
  }()


}

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协程之间进行通信。

例如,

package main

import (
	"context"
	"sync"
)

func main() {

	ctx, cancel := context.WithCancel(context.Background())
	wg := sync.WaitGroup{}
	wg.Add(3)
	go func() {
		defer wg.Done()
		for {
			select {
			// 来自其他Go协程的消息完成
			case <-ctx.Done():
				// 结束
			}
		}
	}()

	go func() {
		defer wg.Done()
		for {
			select {
			// 来自其他Go协程的消息完成
			case <-ctx.Done():
				// 结束
			}
		}
	}()

	go func() {
		defer wg.Done()
		// 进行你的操作
		// 当这个Go协程结束时调用cancel
		cancel()
	}()
	wg.Wait()
}

英文:

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

package main

import (
	&quot;context&quot;
	&quot;sync&quot;
)

func main() {

	ctx, cancel := context.WithCancel(context.Background())
	wg := sync.WaitGroup{}
	wg.Add(3)
	go func() {
		defer wg.Done()
		for {
			select {
			// msg from other goroutine finish
			case &lt;-ctx.Done():
				// end
			}
		}
	}()

	go func() {
		defer wg.Done()
		for {
			select {
			// msg from other goroutine finish
			case &lt;-ctx.Done():
				// end
			}
		}
	}()

	go func() {
		defer wg.Done()
		// your operation
		// call cancel when this goroutine ends
		cancel()
	}()
	wg.Wait()
}

答案2

得分: 2

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

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

done1 := make(chan struct{}) // 当goroutine 1返回时关闭
done2 := make(chan struct{}) // 当goroutine 2返回时关闭

go func() {
	defer close(done1)

    timer1 := time.NewTicker(1 * time.Second)
    defer timer1.Stop()

    timer2 := time.NewTicker(2 * time.Second)
    defer timer2.Stop()

	for {
		select {
		case <-done2:
            // 另一个goroutine已返回。
			fmt.Println("done func 1")
			return
		case <-timer1.C:
			fmt.Println("timer1 func 1")
		case <-timer2.C:
			fmt.Println("timer2 func 1")
			return
		}

	}
}()

go func() {
	defer close(done2)
	for {
		select {
		case <-done1:
            // 另一个goroutine已返回。
			fmt.Println("done func 2")
			return
		default:
			time.Sleep(3 * time.Second)
			fmt.Println("sleep done from func 2")
			return
		}

	}
}()

fmt.Println("等待goroutine完成")

// 等待两个goroutine返回。这里等待的顺序无关紧要。
<-done1
<-done2

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.

done1 := make(chan struct{}) // closed when goroutine 1 returns
done2 := make(chan struct{}) // closed when goroutine 2 returns

go func() {
	defer close(done1)

    timer1 := time.NewTicker(1 * time.Second)
    defer timer1.Stop()

    timer2 := time.NewTicker(2 * time.Second)
    defer timer2.Stop()

	for {
		select {
		case &lt;-done2:
            // The other goroutine returned.
			fmt.Println(&quot;done func 1&quot;)
			return
		case &lt;-timer1.C:
			fmt.Println(&quot;timer1 func 1&quot;)
		case &lt;-timer2.C:
			fmt.Println(&quot;timer2 func 1&quot;)
			return
		}

	}
}()

go func() {
	defer close(done2)
	for {
		select {
		case &lt;-done1:
            // The other goroutine returned.
			fmt.Println(&quot;done func 2&quot;)
			return
		default:
			time.Sleep(3 * time.Second)
			fmt.Println(&quot;sleep done from func 2&quot;)
			return
		}

	}
}()

fmt.Println(&quot;waiting for goroutines to complete&quot;)

// Wait for both goroutines to return. The order that
// we wait here does not matter. 
&lt;-done1
&lt;-done2

fmt.Println(&quot;all done&quot;)

Run it on the playground.

答案3

得分: 2

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

使用sync.WaitGroup来协调goroutine。

func main() {
	wait := &sync.WaitGroup{}
	N := 3

	wait.Add(N)
	for i := 1; i <= N; i++ {
		go goFunc(wait, i, true)
	}

	wait.Wait()
	fmt.Println(`Exiting main`)
}

每个goroutine的代码如下:

// 实际goroutine的代码
func goFunc(wait *sync.WaitGroup, i int, closer bool) {
	defer wait.Done()
	defer fmt.Println(`Exiting `, i)

	T := time.Tick(time.Duration(100*i) * time.Millisecond)
	for {
		select {
		case <-T:
			fmt.Println(`Tick `, i)
			if closer {
				return
			}
		}
	}
}

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

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

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

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

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

func goFunc(wait *sync.WaitGroup, i int, closer bool, CLOSE chan struct{}) {
	defer wait.Done()
	defer fmt.Println(`Exiting `, i)

	T := time.Tick(time.Duration(100*i) * time.Millisecond)
	for {
		select {
		case <-CLOSE:
			return
		case <-T:
			fmt.Println(`Tick `, i)
			if closer {
				close(CLOSE)
			}
		}
	}
}

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

func main() {
	wait := &sync.WaitGroup{}
	N := 3
	CLOSE := make(chan struct{})

	// 启动goroutine
	wait.Add(N)
	for i := 1; i <= N; i++ {
		go goFunc(wait, i, i == N, CLOSE)
	}

	// 等待goroutine完成
	wait.Wait()
	fmt.Println(`Exiting main`)
}

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

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

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

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

func main() {
	wait := &sync.WaitGroup{}
	N := 3
	CLOSE := make(chan struct{})

	// 启动goroutine
	wait.Add(N)
	for i := 1; i <= N; i++ {
		go goFunc(wait, i, true /*** 每个goroutine都会关闭 ***/, CLOSE)
	}

	// 等待goroutine完成
	wait.Wait()
	fmt.Println(`Exiting main`)
}

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

// 实际goroutine的代码
func goFunc(wait *sync.WaitGroup, i int, closer bool, CLOSE chan struct{}) {
	defer wait.Done()
	defer fmt.Println(`Exiting `, i)

	T := time.Tick(time.Duration(100*i) * time.Millisecond)
	for {
		select {
		case <-CLOSE:
			return
		case <-T:
			fmt.Println(`Tick `, i)
			if closer {
				/*** 在关闭之前等待一段时间 ***/
				time.Sleep(time.Second)
				close(CLOSE)
			}
		}
	}
}

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

我们遇到了崩溃:

Tick  1
Tick  2
Tick  3
Exiting  1
Exiting  2
panic: close of closed channel

goroutine 7 [running]:
main.goFunc(0x40e020, 0x2, 0x68601, 0x430080)
	/tmp/sandbox558886627/prog.go:24 +0x2e0
created by main.main
	/tmp/sandbox558886627/prog.go:38 +0xc0

Program exited: status 2.

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

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

type Close struct {
	C chan struct{}
	l sync.Mutex
	closed bool
}

func NewClose() *Close {
	return &Close {
		C: make(chan struct{}),
	}
}

func (c *Close) Close() {
	c.l.Lock()
	if (!c.closed) {
		c.closed=true
		close(c.C)
	}
	c.l.Unlock()
}

重写我们的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.

func main() {
	wait := &amp;sync.WaitGroup{}
	N := 3

	wait.Add(N)
	for i := 1; i &lt;= N; i++ {
		go goFunc(wait, i, true)
	}

	wait.Wait()
	fmt.Println(`Exiting main`)
}

Each goroutine will look like this:

// code for the actual goroutine
func goFunc(wait *sync.WaitGroup, i int, closer bool) {
	defer wait.Done()
	defer fmt.Println(`Exiting `, i)

	T := time.Tick(time.Duration(100*i) * time.Millisecond)
	for {
		select {
		case &lt;-T:
			fmt.Println(`Tick `, i)
			if closer {
				return
			}
		}
	}
}

(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:

func goFunc(wait *sync.WaitGroup, i int, closer bool, CLOSE chan struct{}) {
	defer wait.Done()
	defer fmt.Println(`Exiting `, i)

	T := time.Tick(time.Duration(100*i) * time.Millisecond)
	for {
		select {
		case &lt;-CLOSE:
			return
		case &lt;-T:
			fmt.Println(`Tick `, i)
			if closer {
				close(CLOSE)
			}
		}
	}
}

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:

func main() {
	wait := &amp;sync.WaitGroup{}
	N := 3
	CLOSE := make(chan struct{})

	// Launch the goroutines
	wait.Add(N)
	for i := 1; i &lt;= N; i++ {
		go goFunc(wait, i, i == N, CLOSE)
	}

	// Wait for the goroutines to finish
	wait.Wait()
	fmt.Println(`Exiting main`)
}

(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:

func main() {
	wait := &amp;sync.WaitGroup{}
	N := 3
	CLOSE := make(chan struct{})

	// Launch the goroutines
	wait.Add(N)
	for i := 1; i &lt;= N; i++ {
		go goFunc(wait, i, true /*** EVERY GOROUTINE WILL CLOSE ***/, CLOSE)
	}

	// Wait for the goroutines to finish
	wait.Wait()
	fmt.Println(`Exiting main`)
}

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

// code for the actual goroutine
func goFunc(wait *sync.WaitGroup, i int, closer bool, CLOSE chan struct{}) {
	defer wait.Done()
	defer fmt.Println(`Exiting `, i)

	T := time.Tick(time.Duration(100*i) * time.Millisecond)
	for {
		select {
		case &lt;-CLOSE:
			return
		case &lt;-T:
			fmt.Println(`Tick `, i)
			if closer {
				/*** TAKE A WHILE BEFORE CLOSING ***/
				time.Sleep(time.Second)
				close(CLOSE)
			}
		}
	}
}


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

We crash with:

Tick  1
Tick  2
Tick  3
Exiting  1
Exiting  2
panic: close of closed channel

goroutine 7 [running]:
main.goFunc(0x40e020, 0x2, 0x68601, 0x430080)
	/tmp/sandbox558886627/prog.go:24 +0x2e0
created by main.main
	/tmp/sandbox558886627/prog.go:38 +0xc0

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:

type Close struct {
	C chan struct{}
	l sync.Mutex
	closed bool
}

func NewClose() *Close {
	return &amp;Close {
		C: make(chan struct{}),
	}
}

func (c *Close) Close() {
	c.l.Lock()
	if (!c.closed) {
		c.closed=true
		close(c.C)
	}
	c.l.Unlock()
}

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通道分开。

import `sync`

// 这段代码将在两个函数完成之前等待,然后结束
func main {
   var wait sync.WaitGroup
   wait.Add(2)
   go func() {
     defer wait.Done()
   }()
   go g() {
     defer wait.Done()
   }()
   wait.Wait()
}

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

package main

import (
	`fmt`
	`sync`
	`time`
)

// WaitForIt 封装了一个Cond和一个Mutex,提供了一个更简单的API:
// .WAIT() chan struct{} 返回一个通道,当WaitForIt完成时会发出信号。
// .Done() 表示WaitForIt完成。
type WaitForIt struct {
	L *sync.Mutex
	Cond *sync.Cond
}

func NewWaitForIt() *WaitForIt {
	l := &amp;sync.Mutex{}
	c := sync.NewCond(l)
	return &amp;WaitForIt{ l, c }
}

// WAIT 返回一个通道,当Cond触发时会发出信号。
func (w *WaitForIt) WAIT() chan struct{} {
	D := make(chan struct{})
	go func() {
		w.L.Lock()
		defer w.L.Unlock()
		w.Cond.Wait()
		D &lt;- struct{}{}
		close(D)
	}()
	return D
}

// Done 表示Cond应该被触发。
func (w *WaitForIt) Done() {
	w.Cond.Broadcast()
}

// doneFunc 使用一个通道来启动函数f,当函数应该停止时,通道会发出信号。
// 它还处理WaitGroup的同步。
func doneFunc(wait *sync.WaitGroup, waitForIt *WaitForIt, f func(DONE chan struct{})) {
	defer wait.Done()
	f(waitForIt.WAIT())
}

func main() {
	// wait 将在main()函数级别协调所有的goroutine
    // waitForIt 将协调所有的goroutine
	wait := &amp;sync.WaitGroup{}
	// waitForIt 用于指示goroutine何时关闭
	waitForIt := NewWaitForIt()

    // goFunc 生成每个goroutine。只有3秒的goroutine会关闭所有的goroutine
	goFunc := func(seconds int) func(chan struct{}) {
		return func(DONE chan struct{}) {
            // 这是每个goroutine的实际代码
            // 它创建一个计时器,持续一定的秒数,
            // 在计时器结束后打印秒数,
            // 或者在DONE被触发时退出
			timer := time.NewTicker(time.Duration(seconds) * time.Second)
			defer timer.Stop()
			for {
				select {
				case &lt;- DONE:
					return
				case &lt;- timer.C:
					if (3==seconds) {
						waitForIt.Done()
                        // 不要在这里关闭 - 我们将在DONE被触发时关闭
					}
				}
			}
		}
	}
	// 启动3个goroutine,每个都在等待关闭信号
	for i:=1; i&lt;=3; i++ {
		wait.Add(1)
		go doneFunc(wait, waitForIt, goFunc(i))
	}
	// 等待所有的goroutine完成,然后我们完成了
	wait.Wait()
}

这是使用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.

import `sync`

// This code will wait for the two functions to complete before ending
func main {
   var wait sync.WaitGroup
   wait.Add(2)
   go func() {
     defer wait.Done()
   }()
   go g() {
     defer wait.Done()
   }()
   wait.Wait()
}

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:

package main

import (
	`fmt`
	`sync`
	`time`
)

// WaitForIt wraps a Cond and a Mutex for a simpler API:
// .WAIT() chan struct{} will return a channel that will be
//   signalled when the WaitForIt is done.
// .Done() will indicate that the WaitForIt is done.
type WaitForIt struct {
	L *sync.Mutex
	Cond *sync.Cond
}

func NewWaitForIt() *WaitForIt {
	l := &amp;sync.Mutex{}
	c := sync.NewCond(l)
	return &amp;WaitForIt{ l, c }
}

// WAIT returns a chan that will be signalled when
// the Cond is triggered.
func (w *WaitForIt) WAIT() chan struct{} {
	D := make(chan struct{})
	go func() {
		w.L.Lock()
		defer w.L.Unlock()
		w.Cond.Wait()
		D &lt;- struct{}{}
		close(D)
	}()
	return D
}

// Done indicates that the Cond should be triggered.
func (w *WaitForIt) Done() {
	w.Cond.Broadcast()
}

// doneFunc launches the func f with a chan that will be signalled when the
// func should stop. It also handles WaitGroup synchronization
func doneFunc(wait *sync.WaitGroup, waitForIt *WaitForIt, f func(DONE chan struct{})) {
	defer wait.Done()
	f(waitForIt.WAIT())
}

func main() {
	// wait will coordinate all the goroutines at the level of main()
    // between themselves the waitForIt will do the coordination
	wait := &amp;sync.WaitGroup{}
	// waitForIt indicates to the goroutines when they should shut
	waitForIt := NewWaitForIt()

    // goFunc generates each goroutine. Only the 3-second goroutine will 
    // shutdown all goroutines
	goFunc := func(seconds int) func(chan struct{}) {
		return func(DONE chan struct{}) {
            // this is the actual code of each goroutine
            // it makes a ticker for a number of seconds,
            // and prints the seconds after the ticker elapses,
            // or exits if DONE is triggered
			timer := time.NewTicker(time.Duration(seconds) * time.Second)
			defer timer.Stop()
			for {
				select {
				case &lt;- DONE:
					return
				case &lt;- timer.C:
					if (3==seconds) {
						waitForIt.Done()
                        // Don&#39;t shutdown here - we&#39;ll shutdown
                        // when our DONE is signalled
					}
				}
			}
		}
	}
	// launch 3 goroutines, each waiting on a shutdown signal
	for i:=1; i&lt;=3; i++ {
		wait.Add(1)
		go doneFunc(wait, waitForIt, goFunc(i))
	}
	// wait for all the goroutines to complete, and we&#39;re done
	wait.Wait()
}

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

package main

import (
	"fmt"
	"sync"
	"time"
)

func func1(done chan struct{}, wg *sync.WaitGroup) {
	defer wg.Done()
	timer1 := time.NewTicker(1 * time.Second)
	timer2 := time.NewTicker(2 * time.Second)
	for {
		select {
		case <-timer1.C:
			fmt.Println("定时器1函数1")
		case <-timer2.C:
			// 请求GC清理定时器timer1和timer2
			// 因为goroutine即将返回
			timer1.Stop()
			timer2.Stop()

			fmt.Println("定时器2函数1")

			done <- struct{}{} // 通知另一个goroutine终止

			fmt.Println("从函数1发送done")
			return
		case <-done:
			// 请求GC清理定时器timer1和timer2
			// 因为goroutine即将返回
			timer1.Stop()
			timer2.Stop()

			fmt.Println("函数1完成")
			return

		}

	}
}

func func2(done chan struct{}, wg *sync.WaitGroup) {
	defer wg.Done()
	timer3 := time.NewTicker(3 * time.Second)
	for {
		select {
		case <-timer3.C:
			// 请求GC清理定时器timer3
			// 因为goroutine即将返回
			timer3.Stop()

			fmt.Println("定时器3函数2")

			done <- struct{}{} // 通知另一个goroutine终止

			fmt.Println("从函数2发送done")
			return
		case <-done:
			// 请求GC清理定时器timer3
			// 因为goroutine即将返回
			timer3.Stop()
			fmt.Println("函数2完成")
			return
		}

	}
}

func main() {
	// 用于goroutine之间的信号通信的通道
	done := make(chan struct{})

	// WaitGroup
	wg := sync.WaitGroup{}

	wg.Add(2)

	// 启动func1的goroutine
	go func1(done, &wg)
	// 启动func2的goroutine
	go func2(done, &wg)

	fmt.Println("开始休眠")

	// 等待goroutine完成
	wg.Wait()

	// 等待15秒钟
	// 如果不需要,请删除下面的代码
	time.Sleep(15 * time.Second)
	fmt.Println("等待了15秒钟")

}
英文:
package main

import (
	&quot;fmt&quot;
	&quot;sync&quot;
	&quot;time&quot;
)

func func1(done chan struct{}, wg *sync.WaitGroup) {
	defer wg.Done()
	timer1 := time.NewTicker(1 * time.Second)
	timer2 := time.NewTicker(2 * time.Second)
	for {
		select {
		case &lt;-timer1.C:
			fmt.Println(&quot;timer1 func 1&quot;)
		case &lt;-timer2.C:
			// Ask GC to sweep the tickers timer1, timer2
			// as goroutine should return
			timer1.Stop()
			timer2.Stop()

			fmt.Println(&quot;timer2 func 1&quot;)

			done &lt;- struct{}{} // Signal the other goroutine to terminate

			fmt.Println(&quot;sent done from func 1&quot;)
			return
		case &lt;-done:
			// Ask GC to sweep the tickers timer1, timer2
			// as goroutine should return
			timer1.Stop()
			timer2.Stop()

			fmt.Println(&quot;done func 1&quot;)
			return

		}

	}
}

func func2(done chan struct{}, wg *sync.WaitGroup) {
	defer wg.Done()
	timer3 := time.NewTicker(3 * time.Second)
	for {
		select {
		case &lt;-timer3.C:
			// Ask GC to sweep the tickers timer3
			// as goroutine should return
			timer3.Stop()

			fmt.Println(&quot;timer3 func 2&quot;)

			done &lt;- struct{}{} // Signal the other goroutine to terminate

			fmt.Println(&quot;sent done from func 2&quot;)
			return
		case &lt;-done:
			// Ask GC to sweep the tickers timer3
			// as goroutine should return
			timer3.Stop()
			fmt.Println(&quot;done func 2&quot;)
			return
		}

	}
}

func main() {
	// Chan used for signalling between goroutines
	done := make(chan struct{})

	// WaitGroup
	wg := sync.WaitGroup{}

	wg.Add(2)

	// Spawn the goroutine for func1
	go func1(done, &amp;wg)
	// Spawn the goroutine for func2
	go func2(done, &amp;wg)

	fmt.Println(&quot;starting sleep&quot;)

	// Wait for the goroutines
	wg.Wait()

	// Wait for 15 seconds
	// If not required, please remove
	// the lines below
	time.Sleep(15 * time.Second)
	fmt.Println(&quot;waited 15 seconds&quot;)

}

答案6

得分: 0

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

package main

import (
	"os"
	"os/signal"
	"sync"
	"syscall"
)

type RunGroup struct {
	sync.WaitGroup
}

// Run处理等待组状态
func (runGroup *RunGroup) Run(f func()) {
	runGroup.Add(1)
	go func() {
		f()
		runGroup.Done()
	}()
}

func doStuff(done <-chan any, id int) {
	println("Doing something", id)
	<-done
	println("DONE", id)
}

func main() {
	// Done通道
	done := make(chan any)
	// 设置关闭监听器
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGTERM)
	signal.Notify(sigChan, syscall.SIGINT)
	go func() {
		rawSig := <-sigChan
		sig := rawSig.String()
		println("Caught signal, shutting down.", sig)
		close(done)
	}()

	runGroup := RunGroup{}
	// 做一些事情
	runGroup.Run(func() {
		doStuff(done, 1)
	})
	runGroup.Run(func() {
		doStuff(done, 2)
	})
	runGroup.Run(func() {
		doStuff(done, 3)
	})

	// 主线程等待中断
	runGroup.Wait()
}

输出

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

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

package main

import (
	&quot;os&quot;
	&quot;os/signal&quot;
	&quot;sync&quot;
	&quot;syscall&quot;
)


type RunGroup struct {
  sync.WaitGroup
}

// Run handles wait group state
func (runGroup *RunGroup) Run(f func()) {
  runGroup.Add(1)
  go func() {
    f()
    runGroup.Done()
  }()
}

func doStuff(done &lt;-chan any, id int) {
  println(&quot;Doing something&quot;, id)
  &lt;-done
  println(&quot;DONE&quot;, id)
}

func main() {
  // Done channel
  done := make(chan any)
  // Setup Shutdown listeners
  sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGTERM)
	signal.Notify(sigChan, syscall.SIGINT)
  go func() {
    rawSig := &lt;-sigChan
    sig := rawSig.String()
    println(&quot;Caught signal, shutting down.&quot;, sig)
    close(done)
  }()

  runGroup := RunGroup{}
  // Do some stuff
  runGroup.Run(func () {
    doStuff(done, 1)
  })
  runGroup.Run(func () {
    doStuff(done, 2)
  })
  runGroup.Run(func () {
    doStuff(done, 3)
  })

  // Wait mainthread until interrupt
  runGroup.Wait()
}

Output

go run ./main.go
Doing something 3
Doing something 2
Doing something 1
^CCaught signal, shutting down. interrupt
DONE 3
DONE 1
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:

确定