为什么Go的通道是阻塞而不是退出?

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

Why Go channel is blocking but not quitting

问题

我有两个版本来实现使用signal.NotifyContext在信号上进行上下文取消。

版本1 https://play.golang.org/p/rwOnYEgPecE

func main() {
    ch := run()
    <-ch
}

func run() chan bool {
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
    var done = make(chan bool)

    //go func() {
    select {
    case <-ctx.Done():
        fmt.Println("Quitting")
        stop()
        done <- true
    }
    //}()
    return done
}

版本2 https://play.golang.org/p/oijbICeSrNT

func main() {
    ch := run()
    <-ch
}

func run() chan bool {
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
    var done = make(chan bool)

    go func() {
       select {
       case <-ctx.Done():
          fmt.Println("Quitting")
          stop()
          done <- true
       }
    }()
    return done
}

为什么第一个版本打印出Quitting这一行,但没有退出,而第二个版本打印并正确退出?

英文:

I have those two versions to implement context cancellation over signals using signal.NotifyContext

Version 1 https://play.golang.org/p/rwOnYEgPecE

func main() {
	ch := run()
	&lt;-ch
}

func run() chan bool {
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
	var done = make(chan bool)

	//go func() {
	select {
	case &lt;-ctx.Done():
		fmt.Println(&quot;Quitting&quot;)
		stop()
		done &lt;- true
	}
	//}()
	return done
}

Version 2 https://play.golang.org/p/oijbICeSrNT

func main() {
	ch := run()
	&lt;-ch
}

func run() chan bool {
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
	var done = make(chan bool)

	go func() {
	   select {
	   case &lt;-ctx.Done():
		  fmt.Println(&quot;Quitting&quot;)
		  stop()
		  done &lt;- true
	   }
	}()
	return done
}

Why is the first version printing the line Quitting but does not exit, while the second version prints and quits properly?

答案1

得分: 3

第一个案例不按照你的期望行为的原因是因为所有的代码都在一个单独的(主)goroutine中运行。

select {
   case <-ctx.Done():
      fmt.Println("Quitting")
      stop()
      done <- true  // 这里会阻塞,因为没有人在监听
   }
}

在第二个例子中,由于上述逻辑在一个goroutine中运行,所以main()函数将会监听该通道。

第二种方法更好,因为任何信号处理程序(在程序的整个生命周期内运行)都应该在自己的goroutine中运行。

英文:

The reason the first case doesn't behave as you expect is because everything is running in a single (main) goroutine.

select {
   case &lt;-ctx.Done():
      fmt.Println(&quot;Quitting&quot;)
      stop()
      done &lt;- true  // this blocks because no one is listening
   }
}

in your second example because the above logic is run in a goroutine, then main() will be listening on the channel.

The second method is preferred, since any signal handler - which is running for the lifetime of a program - should be running in its own goroutine.

huangapple
  • 本文由 发表于 2021年9月13日 01:32:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/69153806.html
匿名

发表评论

匿名网友

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

确定