Golang 模式一次杀死多个 goroutine

我有两个 goroutine,如下面的代码片段所示。我想同步它们,这样当一个返回时,另一个也应该退出。实现这一目标的最佳方法是什么?


func main() {


  go func() {

    ...

    if err != nil {

      return

    }

  }()


  go func() {

    ...

    if err != nil {

      return

    }

  }()



}

我在这里模拟了这个场景https://play.golang.org/p/IqawStXt7rt并试图用一个通道来解决它,以表示一个例程已经完成。这看起来可能会写入已关闭的通道,从而导致恐慌。解决此问题的最佳方法是什么?


守候你守候我
浏览 191回答 3
3回答

慕神8447489

您可以使用上下文在两个 go 例程之间进行通信。例如,package mainimport (&nbsp; &nbsp; "context"&nbsp; &nbsp; "sync")func main() {&nbsp; &nbsp; ctx, cancel := context.WithCancel(context.Background())&nbsp; &nbsp; wg := sync.WaitGroup{}&nbsp; &nbsp; wg.Add(3)&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; defer wg.Done()&nbsp; &nbsp; &nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // msg from other goroutine finish&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case <-ctx.Done():&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // end&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }()&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; defer wg.Done()&nbsp; &nbsp; &nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // msg from other goroutine finish&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case <-ctx.Done():&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // end&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }()&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; defer wg.Done()&nbsp; &nbsp; &nbsp; &nbsp; // your operation&nbsp; &nbsp; &nbsp; &nbsp; // call cancel when this goroutine ends&nbsp; &nbsp; &nbsp; &nbsp; cancel()&nbsp; &nbsp; }()&nbsp; &nbsp; wg.Wait()}

慕田峪4524236

在通道上使用 close 表示完成。这允许多个 goroutine 通过在通道上接收来检查完成情况。每个 goroutine 使用一个通道来表示 goroutine 的完成。done1 := make(chan struct{}) // closed when goroutine 1 returnsdone2 := make(chan struct{}) // closed when goroutine 2 returnsgo func() {&nbsp; &nbsp; defer close(done1)&nbsp; &nbsp; timer1 := time.NewTicker(1 * time.Second)&nbsp; &nbsp; defer timer1.Stop()&nbsp; &nbsp; timer2 := time.NewTicker(2 * time.Second)&nbsp; &nbsp; defer timer2.Stop()&nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; case <-done2:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // The other goroutine returned.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Println("done func 1")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; case <-timer1.C:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Println("timer1 func 1")&nbsp; &nbsp; &nbsp; &nbsp; case <-timer2.C:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Println("timer2 func 1")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}()go func() {&nbsp; &nbsp; defer close(done2)&nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; case <-done1:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // The other goroutine returned.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Println("done func 2")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; default:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time.Sleep(3 * time.Second)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Println("sleep done from func 2")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}()fmt.Println("waiting for goroutines to complete")// Wait for both goroutines to return. The order that// we wait here does not matter.&nbsp;<-done1<-done2fmt.Println("all done")

喵喵时光机

首先将等待 go-routines 和donechannel 分开。使用 async.WaitGroup来协调 goroutine。func main() {&nbsp; &nbsp; wait := &sync.WaitGroup{}&nbsp; &nbsp; N := 3&nbsp; &nbsp; wait.Add(N)&nbsp; &nbsp; for i := 1; i <= N; i++ {&nbsp; &nbsp; &nbsp; &nbsp; go goFunc(wait, i, true)&nbsp; &nbsp; }&nbsp; &nbsp; wait.Wait()&nbsp; &nbsp; fmt.Println(`Exiting main`)}每个 goroutine 将如下所示:// code for the actual goroutinefunc goFunc(wait *sync.WaitGroup, i int, closer bool) {&nbsp; &nbsp; defer wait.Done()&nbsp; &nbsp; defer fmt.Println(`Exiting `, i)&nbsp; &nbsp; T := time.Tick(time.Duration(100*i) * time.Millisecond)&nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; case <-T:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(`Tick `, i)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if closer {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}(https://play.golang.org/p/mDO4P56lzBU)我们的 main 函数在退出之前成功地等待 goroutines 退出。每个 goroutine 都在关闭自己,我们想要一种同时取消所有 goroutine 的方法。我们将使用chan, 并利用从频道接收的这一特性:引用:关闭通道上的接收操作总是可以立即进行,在接收到任何先前发送的值之后产生元素类型的零值。(https://golang.org/ref/spec#Receive_operator)我们修改我们的 goroutine 来检查 CLOSE:func goFunc(wait *sync.WaitGroup, i int, closer bool, CLOSE chan struct{}) {&nbsp; &nbsp; defer wait.Done()&nbsp; &nbsp; defer fmt.Println(`Exiting `, i)&nbsp; &nbsp; T := time.Tick(time.Duration(100*i) * time.Millisecond)&nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; case <-CLOSE:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; case <-T:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(`Tick `, i)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if closer {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; close(CLOSE)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}然后我们改变我们的func main,让它通过 CLOSE 通道,我们将设置closer变量,以便只有我们的最后一个 goroutine 会触发关闭:func main() {&nbsp; &nbsp; wait := &sync.WaitGroup{}&nbsp; &nbsp; N := 3&nbsp; &nbsp; CLOSE := make(chan struct{})&nbsp; &nbsp; // Launch the goroutines&nbsp; &nbsp; wait.Add(N)&nbsp; &nbsp; for i := 1; i <= N; i++ {&nbsp; &nbsp; &nbsp; &nbsp; go goFunc(wait, i, i == N, CLOSE)&nbsp; &nbsp; }&nbsp; &nbsp; // Wait for the goroutines to finish&nbsp; &nbsp; wait.Wait()&nbsp; &nbsp; fmt.Println(`Exiting main`)}(https://play.golang.org/p/E91CtRAHDp2)现在看起来一切正常。但事实并非如此。并发很难。这段代码中潜伏着一个错误,正等着在生产中咬你。让我们浮出水面。更改我们的示例,以便每个goroutine 都将关闭:func main() {&nbsp; &nbsp; wait := &sync.WaitGroup{}&nbsp; &nbsp; N := 3&nbsp; &nbsp; CLOSE := make(chan struct{})&nbsp; &nbsp; // Launch the goroutines&nbsp; &nbsp; wait.Add(N)&nbsp; &nbsp; for i := 1; i <= N; i++ {&nbsp; &nbsp; &nbsp; &nbsp; go goFunc(wait, i, true /*** EVERY GOROUTINE WILL CLOSE ***/, CLOSE)&nbsp; &nbsp; }&nbsp; &nbsp; // Wait for the goroutines to finish&nbsp; &nbsp; wait.Wait()&nbsp; &nbsp; fmt.Println(`Exiting main`)}更改 goroutine 以便在关闭之前需要一段时间。我们希望两个 goroutine 同时关闭:// code for the actual goroutinefunc goFunc(wait *sync.WaitGroup, i int, closer bool, CLOSE chan struct{}) {&nbsp; &nbsp; defer wait.Done()&nbsp; &nbsp; defer fmt.Println(`Exiting `, i)&nbsp; &nbsp; T := time.Tick(time.Duration(100*i) * time.Millisecond)&nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; case <-CLOSE:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; case <-T:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(`Tick `, i)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if closer {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; /*** TAKE A WHILE BEFORE CLOSING ***/&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time.Sleep(time.Second)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; close(CLOSE)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}(https://play.golang.org/p/YHnbDpnJCks)我们崩溃:Tick&nbsp; 1Tick&nbsp; 2Tick&nbsp; 3Exiting&nbsp; 1Exiting&nbsp; 2panic: close of closed channelgoroutine 7 [running]:main.goFunc(0x40e020, 0x2, 0x68601, 0x430080)&nbsp; &nbsp; /tmp/sandbox558886627/prog.go:24 +0x2e0created by main.main&nbsp; &nbsp; /tmp/sandbox558886627/prog.go:38 +0xc0Program exited: status 2.虽然关闭通道上的接收立即返回,但您无法关闭关闭的通道。我们需要一点协调。我们可以用 async.Mutex和 abool来表示我们是否关闭了通道。让我们创建一个结构来执行此操作:type Close struct {&nbsp; &nbsp; C chan struct{}&nbsp; &nbsp; l sync.Mutex&nbsp; &nbsp; closed bool}func NewClose() *Close {&nbsp; &nbsp; return &Close {&nbsp; &nbsp; &nbsp; &nbsp; C: make(chan struct{}),&nbsp; &nbsp; }}func (c *Close) Close() {&nbsp; &nbsp; c.l.Lock()&nbsp; &nbsp; if (!c.closed) {&nbsp; &nbsp; &nbsp; &nbsp; c.closed=true&nbsp; &nbsp; &nbsp; &nbsp; close(c.C)&nbsp; &nbsp; }&nbsp; &nbsp; c.l.Unlock()}重写我们的 gofunc 和我们的 main 以使用我们新的 Close 结构,我们很高兴: https: //play.golang.org/p/eH3djHu8EXW并发的问题在于,您总是需要想知道如果另一个“线程”在代码中的其他任何地方会发生什么。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go