Golang中的标记符是否会覆盖已经运行的进程?

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

Do tickers in golang overwrite already running processes?

问题

例如,如果我有这样的代码:

  1. t := time.NewTicker(time.Second*1)
  2. for {
  3. select {
  4. case <-t.C:
  5. runSomeExpensiveFunction() // 耗时超过1秒的函数
  6. }
  7. // ...
  8. }

假设runSomeExpensiveFunction在一个循环中打印从1到1万亿的数字。

runSomeExpensiveFunction是否保证会完成运行,还是每次收到新的tick时,Golang会终止它并重新运行?

我进行了一些手动测试,似乎golang会等待这个函数完成后再进行下一个tick,但这感觉不对。

英文:

For example, if I have something like:

  1. t := time.NewTicker(time.Second*1)
  2. for {
  3. select {
  4. case &lt;-t.C:
  5. runSomeExpensiveFunction() // takes over 1 second
  6. }
  7. // ...
  8. }

And let's say runSomeExpensiveFunction prints numbers from 1 to 1 trillion in a loop.

Is runSomeExpensiveFunction guaranteed to finish running or will Golang kill it and run it again everytime a new tick is received?

I've done some manual testing and it seems that golang will wait for this function to finish before going onto the next tick, but this feels wrong to me.

答案1

得分: 2

首先,Go语言不会随意终止一个goroutine。runSomeExpensiveFunction函数会一直运行直到完成。

其次,根据我们所看到的代码,只会同时运行一个runSomeExpensiveFunction函数。将其视为“每次接收到一个新的tick”是不正确的,因为没有任何东西在“接收”tick。你的select语句只会执行一个分支,然后结束,是for { }循环导致select语句被多次执行,直到runSomeExpensiveFunction函数返回时才会到达for循环的末尾。

你的select语句会调用runSomeExpensiveFunction函数,执行流程会进入该函数,然后select语句就结束了。计时器可能会在其通道上发送下一个tick,但没有任何东西在监听它。只有当函数返回时,执行流程才会到达for { }循环的末尾,然后返回到顶部,遇到select语句,测试读取计时器通道的case分支。

如果你想要同时运行多个函数调用,那就要使用go关键字:

  1. case <-t.C:
  2. go runSomeExpensiveFunction()

需要注意的是,如果该函数执行时间超过一秒,你将面临无限并行性,并且程序最终会崩溃。

英文:

First, Go will never just kill a go routine. runSomeExpensiveFunction will run until it finishes.

Secondly, only one runSomeExpensiveFunction will ever run at a time given the code we can see here. It's incorrect to think of this as "everytime a new tick is received", because nothing is "receiving" a tick. Your select executes one branch and then it's done, it's the for { } that causes the select to be executed many times, and the end of the for isn't reached until runSomeExpensiveFunction returns.

Your select will invoke runSomeExpensiveFunction, and the flow of execution will enter that function, and the select is done. The timer might emit its next tick over its channel, but nothing is listening for it. It's only when your function returns that the flow of execution hits the end of your for { } and returns to the top, where it encounters the select again, and the case that reads from the timer's channel is tested.

If you want to have multiple invocations of your function running concurrently, that's what the go keyword is for:

  1. case &lt;-t.C:
  2. go runSomeExpensiveFunction()

You should note that, if the function takes more than a second, you'll be looking at unbounded parallelism and your program will eventually crash.

答案2

得分: 2

t.C 不是魔法。t.C 返回一个通道。<-t.C 尝试从通道中读取数据。该通道会阻塞一段时间,然后返回一个时间。

select 会选择第一个没有阻塞的通道。在你的代码中,它会等待 t.C 返回一个 tick,然后运行 runSomeExpensiveFunction

例如,以下代码会打印出 1、2,然后超时。

  1. func handle(a int) {
  2. time.Sleep(time.Second*2)
  3. fmt.Println(a)
  4. }
  5. func main() {
  6. t := time.NewTicker(time.Second*1)
  7. c := make(chan int, 2)
  8. c<-1
  9. c<-2
  10. for {
  11. select {
  12. case m := <-c:
  13. handle(m)
  14. case <-t.C:
  15. fmt.Println("timed out")
  16. }
  17. }
  18. }
  1. select 尝试从 ct.C 中读取数据。c 立即返回,所以 handle 函数会运行。
  2. select 尝试从 ct.C 中读取数据。c 立即返回,所以 handle 函数会运行。
  3. select 尝试从 ct.C 中读取数据。c 是空的,所以 <-c 会阻塞。<-t.C 会阻塞一秒钟,然后返回一个时间并打印出 "timed out"。

然后重复步骤 3。

英文:

t.C isn't magic. t.C returns a channel. &lt;-t.C tries to read from the channel. The channel blocks for the duration and then returns a Time.

select picks the first channel which isn't blocking. In your code it will wait until t.C returns a tick and then run runSomeExpensiveFunction.

For example, this will print 1, 2, then time out.

  1. func handle(a int) {
  2. time.Sleep(time.Second*2)
  3. fmt.Println(a)
  4. }
  5. func main() {
  6. t := time.NewTicker(time.Second*1)
  7. c := make(chan int, 2)
  8. c&lt;-1
  9. c&lt;-2
  10. for {
  11. select {
  12. case m := &lt;-c:
  13. handle(m)
  14. case &lt;-t.C:
  15. fmt.Println(&quot;timed out&quot;)
  16. }
  17. }
  18. }
  1. select tries to read from c and t.C. c returns immediately so handle runs.
  2. select tries to read from c and t.C. c returns immediately so handle runs.
  3. select tries to read from c and t.C. c is empty so &lt;-c blocks. &lt;-t.C blocks for a second and returns a Time and prints timed out.

And it repeats step 3.

huangapple
  • 本文由 发表于 2022年3月5日 09:11:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/71358708.html
匿名

发表评论

匿名网友

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

确定