“done”通道和默认情况是否会导致goroutine泄漏?

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

Do the 'done' channel and default case lead to goroutine leak

问题

有两个类似的案例我想与您进行比较 - 唯一的区别是值生成的方式。

  • 第一个案例:在select的一个case中生成值
package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	generateValues := func(done <-chan interface{}) <-chan int {
		values := make(chan int)
		go func() {
			defer fmt.Println("All values generated")
			defer close(values)
			for {
				select {
				case <-done:
					fmt.Println("DONE")
					return
				case values <- rand.Int():
					fmt.Println("Generated")
				}

			}

		}()
		return values
	}

	done := make(chan interface{})
	values := generateValues(done)

	for i := 0; i < 3; i++ {
		fmt.Printf("Received value: %v\n", <-values)
	}
	fmt.Println("Closing the channel")
	close(done)
	time.Sleep(5 * time.Second)
}

Go playground: https://go.dev/play/p/edlOSqdZ9ys

  • 第二个案例:在default case中生成值
package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	generateValues := func(done <-chan interface{}) <-chan int {
		values := make(chan int)
		go func() {
			defer fmt.Println("All values generated")
			defer close(values)
			for {
				select {
				case <-done:
					fmt.Println("DONE")
					return
				default:
					values <- rand.Int()
					fmt.Println("Generated")
				}

			}

		}()
		return values
	}

	done := make(chan interface{})
	values := generateValues(done)

	for i := 0; i < 3; i++ {
		fmt.Printf("Received value: %v\n", <-values)
	}
	fmt.Println("Closing the channel")
	close(done)
	time.Sleep(5 * time.Second)
}

Go playground: https://go.dev/play/p/edlOSqdZ9ys

正如您所看到的,第二个案例似乎导致了'Done'未打印和与'defer'相关的调用未被调用的情况。我相信这里存在goroutine泄漏,但无法清楚地解释。我期望的行为与第一个案例相同。

有人可以帮助理解它们之间的区别吗?

英文:

There are two similar cases I would like to compare with you - the only difference is the way of handling values generation

  • the first case: values generation in one of case of select
package main

import (
	&quot;fmt&quot;
	&quot;math/rand&quot;
	&quot;time&quot;
)

func main() {
	generateValues := func(done &lt;-chan interface{}) &lt;-chan int {
		values := make(chan int)
		go func() {
			defer fmt.Println(&quot;All values generated&quot;)
			defer close(values)
			for {
				select {
				case &lt;-done:
					fmt.Println(&quot;DONE&quot;)
					return
				case values &lt;- rand.Int():
					fmt.Println(&quot;Generated&quot;)
				}

			}

		}()
		return values
	}

	done := make(chan interface{})
	values := generateValues(done)

	for i := 0; i &lt; 3; i++ {
		fmt.Printf(&quot;Received value: %v\n&quot;, &lt;-values)
	}
	fmt.Println(&quot;Closing the channel&quot;)
	close(done)
	time.Sleep(5 * time.Second)
}

Go playground: https://go.dev/play/p/edlOSqdZ9ys

  • the second case: value generation in default case
package main

import (
	&quot;fmt&quot;
	&quot;math/rand&quot;
	&quot;time&quot;
)

func main() {
	generateValues := func(done &lt;-chan interface{}) &lt;-chan int {
		values := make(chan int)
		go func() {
			defer fmt.Println(&quot;All values generated&quot;)
			defer close(values)
			for {
				select {
				case &lt;-done:
					fmt.Println(&quot;DONE&quot;)
					return
				default:
					values &lt;- rand.Int()
					fmt.Println(&quot;Generated&quot;)
				}

			}

		}()
		return values
	}

	done := make(chan interface{})
	values := generateValues(done)

	for i := 0; i &lt; 3; i++ {
		fmt.Printf(&quot;Received value: %v\n&quot;, &lt;-values)
	}
	fmt.Println(&quot;Closing the channel&quot;)
	close(done)
	time.Sleep(5 * time.Second)
}

Go playground: https://go.dev/play/p/edlOSqdZ9ys

As you can see the second case seems leading to the situation that the 'Done' is not printed and calls related to 'defer' are not invoked. I believe we have here goroutine leaks, but cannot clearly explain it. I expected the same behaviour like in the first case.

Could someone please help in understanding the difference between them?

答案1

得分: 1

在第二种情况下,生成的 goroutine 不太可能接收到 done 消息。由于 default 分支总是被执行,当主 goroutine 接收到最后一个值后,生成的 goroutine 会再次执行,进入 default 分支,并阻塞等待写入 values 通道。在等待期间,主 goroutine 关闭了 done 通道并终止。

这并不意味着不存在一种情况,生成的 goroutine 不接收 done 通道。要发生这种情况,必须在发送最后一个值后,生成的 goroutine 被主 goroutine 抢占,并一直运行直到关闭 done 通道。然后,如果生成的 goroutine 被调度,它可以接收到 done 信号。然而,这种事件序列的发生概率非常低。

英文:

In the second case, the generating goroutine is not likely to receive the done message. Since the default case is always enabled, after the main goroutine receives the last value, the generating goroutine makes another round, falls into the default case, and blocks waiting to write to the values channel. While it is waiting there, the main goroutine closes the done channel and terminates.

This doesn't mean there doesn't exist an execution path where the generating goroutine doesn't receive the done channel. For this to happen, immediately after sending the last value, the generating goroutine must be preempted by the main goroutine that runs until it closes the done channel. Then if the generating goroutine is scheduled, it can receive the done signal. However, this sequence of events is highly unlikely.

huangapple
  • 本文由 发表于 2022年1月30日 01:16:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/70907885.html
匿名

发表评论

匿名网友

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

确定