英文:
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 <- 3
strChan <- "hello"
fmt.Println("send")
// comment this, fatal error: all goroutines are asleep - deadlock!
// 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)
}
}
}
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 <- 3
和strChan <- "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 <- 3
and strChan <- "hello"
. 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("another goroutine")
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 (
"fmt"
"time"
)
func main() {
intChan := make(chan int, 1)
strChan := make(chan string, 1)
intChan <- 3
strChan <- "hello"
fmt.Println("send")
// comment this, fatal error: all goroutines are asleep - deadlock!
// 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)
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论