当 Golang 关闭通道时,接收器 goroutine 永远不会被阻塞。

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

when golang close the channel, the receiver goroutine is never blocked

问题

我写了一些代码来学习Go语言的通道(channels),代码如下所示:

func main(){
	intChan := make(chan int, 1)
	strChan := make(chan string, 1)

	intChan <- 3
	strChan <- "hello"
	fmt.Println("send")

	// 注释掉这部分代码,会出现致命错误:所有的goroutine都处于休眠状态 - 死锁!
	// close(intChan)
	// close(strChan)

	for {
		select {
		case e, ok := <-intChan:
			time.Sleep(time.Second * 2)
			fmt.Println("The case intChan ok: ", ok)
			fmt.Println("The case intChan is selected.", e)

		case e, ok := <-strChan:
			time.Sleep(time.Second)
			fmt.Println("The case strChan ok: ", ok)
			fmt.Println("The case strChan is selected.", e)
		}
	}
}

如果我注释掉close()函数,那么for循环语句会被阻塞,就像错误提示所说的“所有的goroutine都处于休眠状态 - 死锁!”一样,这是合理的。

如果我取消注释close()函数,那么for循环语句将永远不会停止。接收者从通道中获取默认值0和nil,并且不会被阻塞。

即使我没有向通道发送任何内容,并且在定义通道后调用close()函数,接收者也不会被阻塞或引发任何错误。

我对close函数的作用感到困惑,它是否会启动一个goroutine来向通道发送特定类型的默认值,并且永远不会停止?

英文:

I wrote some code to learn Go channels, a piece of code like this below:

func main(){
	intChan := make(chan int, 1)
	strChan := make(chan string, 1)

	intChan &lt;- 3
	strChan &lt;- &quot;hello&quot;
	fmt.Println(&quot;send&quot;)

	// comment this, fatal error: all goroutines are asleep - deadlock!
	// close(intChan)
	// close(strChan)

	for {
		select {
		case e, ok := &lt;-intChan:
			time.Sleep(time.Second * 2)
			fmt.Println(&quot;The case intChan ok: &quot;, ok)
			fmt.Println(&quot;The case intChan is selected.&quot;, e)

		case e, ok := &lt;-strChan:
			time.Sleep(time.Second)
			fmt.Println(&quot;The case strChan ok: &quot;, ok)
			fmt.Println(&quot;The case strChan is selected.&quot;, e)
		}
	}
}

If I comment the close() function, the "for" statement is blocked, just as the error says "all goroutines are asleep - deadlock!", it seems reasonable.

If I uncomment the close(), the "for" statement never stops. The receiver gets default 0 and nil from the channel and never blocks.

Even if I send nothing to the channel, and call the close() after the channel is defined. The receiver never blocks or causes any error.

I am confused about what the close function does, does it start a go routine to send the default value of a specific type to the channel, and never stop?

答案1

得分: 1

当通道关闭时,你仍然可以读取它,但ok的值将为false。这就是为什么你的for循环永远不会停止的原因。因此,你需要通过条件if !ok { break }来中断for语句。

当通道未关闭时,当你尝试从通道中读取数据时,它会根据缓冲/非缓冲通道的情况来决定是否阻塞。


缓冲通道:你给定了大小(make(chan int, 1))。
非缓冲通道:你没有给定大小(make(chan int)


在你的情况下,当你发送数据到通道中(intChan <- 3strChan <- "hello"),它将从缓冲通道中读取数据一次。因此,你将看到控制台打印以下结果。

send
The case strChan ok:  true
The case strChan is selected. hello
The case intChan ok:  true
The case intChan is selected. 3

但是,在此之后,两个缓冲通道都没有数据了。然后,如果你尝试读取它,你将被阻塞,因为缓冲通道中没有任何数据。(实际上,非缓冲通道在没有数据时也是相同的情况。)

然后,你会得到all goroutines are asleep - deadlock的错误,因为主goroutine被阻塞等待通道中的数据。顺便说一下,当你运行这个程序时,它会启动一个主goroutine来运行你的代码。如果这唯一的一个主goroutine被阻塞,这意味着没有人可以帮助你运行你的代码。

你可以在for语句之前添加以下代码。

go func() {
    fmt.Println("another goroutine")
    for {}
}()

你会发现你不会遇到死锁,因为仍然有一个goroutine“可能”运行你的代码。

英文:

When channel is closed, you still could read it, but your ok will be false. So, that's why your for never stops. And, you need to break for statement by condition if !ok { break }.

When channel is not closed, it will be blocked or not be blocked depend on buffered / unbuffered channel when you try to read data from it.


The buffered channel: you give the size (make(chan int, 1)).
The unbuffered channel: you don't give the size (make(chan int))


In your case which comment close, it will read data from you buffered channel once because you send data to channel by intChan &lt;- 3 and strChan &lt;- &quot;hello&quot;. So, you will see your console print the following result.

send
The case strChan ok:  true
The case strChan is selected. hello
The case intChan ok:  true
The case intChan is selected. 3

But, after that, the both buffered channels don't have data any more. Then, if you try to read it, you will be blocked because there is no any data in buffered channel. (Actually, unbuffered is same situation when there is no data in it.)

Then, you get the all goroutines are asleep - deadlock because the main goroutine is blocked to wait for data from channel. By the way, when you run this program, it will start a main goroutine to run your code. If this only one main goroutine is blocked, that means there is no anyone could help you to run your code.

You could add the following code before for statement.

go func() {
    fmt.Println(&quot;another goroutine&quot;)
    for {}
}()

You will see you don't get the deadlock because there is still a goroutine ""might"" run your code.

答案2

得分: 0

我猜你需要验证该通道是否仍然可以接收消息;如果不能,只需使用"break"退出for循环。

package main

import (
	"fmt"
	"time"
)

func main() {
	intChan := make(chan int, 1)
	strChan := make(chan string, 1)

	intChan <- 3
	strChan <- "hello"
	fmt.Println("send")

	// 注释掉这行,致命错误:所有的goroutine都处于休眠状态 - 死锁!
	// close(intChan)
	// close(strChan)

	for {
		select {
		case e, ok := <-intChan:
			if !ok {
				break
			}
			time.Sleep(time.Second * 2)
			fmt.Println("The case intChan ok: ", ok)
			fmt.Println("The case intChan is selected.", e)
			close(intChan)

		case e, ok := <-strChan:
			if !ok {
				break
			}
			time.Sleep(time.Second)
			fmt.Println("The case strChan ok: ", ok)
			fmt.Println("The case strChan is selected.", e)
			close(strChan)
		}
	}
}
英文:

I guess you need to verify that the channel can still receive messages; if not, simply use "break" to exit from the for-loop

package main

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

func main() {
	intChan := make(chan int, 1)
	strChan := make(chan string, 1)

	intChan &lt;- 3
	strChan &lt;- &quot;hello&quot;
	fmt.Println(&quot;send&quot;)

	// comment this, fatal error: all goroutines are asleep - deadlock!
	// close(intChan)
	// close(strChan)

	for {
		select {
		case e, ok := &lt;-intChan:
			if !ok {
				break
			}
			time.Sleep(time.Second * 2)
			fmt.Println(&quot;The case intChan ok: &quot;, ok)
			fmt.Println(&quot;The case intChan is selected.&quot;, e)
			close(intChan)

		case e, ok := &lt;-strChan:
			if !ok {
				break
			}
			time.Sleep(time.Second)
			fmt.Println(&quot;The case strChan ok: &quot;, ok)
			fmt.Println(&quot;The case strChan is selected.&quot;, e)
			close(strChan)
		}
	}
}

huangapple
  • 本文由 发表于 2023年3月22日 17:27:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/75810425.html
匿名

发表评论

匿名网友

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

确定