我在Golang工作了很长时间。但是尽管我知道问题的解决方案,但我仍然面临着这个问题。但从未弄清楚为什么会这样。
例如,如果我对入站和出站通道有如下管道状况:
package main
import (
"fmt"
)
func main() {
for n := range sq(sq(gen(3, 4))) {
fmt.Println(n)
}
fmt.Println("Process completed")
}
func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func sq(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
这不会给我带来僵局。但是,如果我在出站代码中删除go例程,如下所示:
func sq(in <-chan int) <-chan int {
out := make(chan int)
for n := range in {
out <- n * n
}
close(out)
return out
}
我收到了死锁错误。为什么要使用范围而不使用go例程在通道上循环会导致死锁。
答案 0 :(得分:2)
这种由sq
函数的输出通道引起的情况没有被缓冲。因此sq
正在等待直到下一个函数将从输出中读取,但是如果sq
不是异步的,它将不会发生(Playground link):
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func main() {
numsCh := gen(3, 4)
sqCh := sq(numsCh) // if there is no sq in body - we are locked here until input channel will be closed
result := sq(sqCh) // but if output channel is not buffered, so `sq` is locked, until next function will read from output channel
for n := range result {
fmt.Println(n)
}
fmt.Println("Process completed")
}
func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func sq(in <-chan int) <-chan int {
out := make(chan int, 100)
for n := range in {
out <- n * n
}
close(out)
return out
}
答案 1 :(得分:1)
您的函数创建一个通道,对其进行写入,然后将其返回。写操作将阻塞,直到有人可以读取相应的值为止,但这是不可能的,因为此功能之外的任何人都没有该通道。
var elementValues = document.getElementById("p1");
如果将循环包装在goroutine中,则循环和func sq(in <-chan int) <-chan int {
// Nobody else has this channel yet...
out := make(chan int)
for n := range in {
// ...but this line will block until somebody reads the value...
out <- n * n
}
close(out)
// ...and nobody else can possibly read it until after this return.
return out
}
函数都可以继续;即使循环阻塞,sq
语句仍然可以执行,最终您将能够将阅读器连接到通道。
(在goroutines之外循环访问通道本质上没有什么坏处;您的return out
函数可以无害且正确地做到这一点。)
答案 2 :(得分:0)
死锁的原因是因为主体正在等待sq
返回并完成,但是sq
正在等待某人阅读该chan,然后它才能继续。
我通过删除sq调用层简化了您的代码,并将一个句子分成2个:
func main() {
result := sq(gen(3, 4)) // <-- block here, because sq doesn't return
for n := range result {
fmt.Println(n)
}
fmt.Println("Process completed")
}
func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func sq(in <-chan int) <-chan int {
out := make(chan int)
for n := range in {
out <- n * n // <-- block here, because no one is reading from the chan
}
close(out)
return out
}
在sq方法中,如果将代码放入goroutine
中,则将返回sq
,并且main func将不会阻塞,并占用结果队列,并且goroutine
将继续,那么就不再有障碍了。
func main() {
result := sq(gen(3, 4)) // will not blcok here, because the sq just start a goroutine and return
for n := range result {
fmt.Println(n)
}
fmt.Println("Process completed")
}
func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func sq(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n // will not block here, because main will continue and read the out chan
}
close(out)
}()
return out
}
答案 3 :(得分:0)
代码有点复杂, 让我们简化
下面的第一个 eq,没有死锁
func main() {
send := make(chan int)
receive := make(chan int)
go func() {
send<-3
send<-4
close(send)
}()
go func() {
receive<- <-send
receive<- <-send
close(receive)
}()
for v := range receive{
fmt.Println(v)
}
}
下面的第二个eq,删除“go”有死锁
func main() {
send := make(chan int)
receive := make(chan int)
go func() {
send<-3
send<-4
close(send)
}()
receive<- <-send
receive<- <-send
close(receive)
for v := range receive{
fmt.Println(v)
}
}
让我们再次简化第二个代码
func main() {
ch := make(chan int)
ch <- 3
ch <- 4
close(ch)
for v := range ch{
fmt.Println(v)
}
}
死锁的原因是主 goroutine 中没有缓冲通道等待。
两种解决方案
// add more cap then "channel<-" time
func main() {
ch := make(chan int,2)
ch <- 3
ch <- 4
close(ch)
for v := range ch{
fmt.Println(v)
}
}
//async "<-channel"
func main() {
ch := make(chan int)
go func() {
for v := range ch {
fmt.Println(v)
}
}()
ch <- 3
ch <- 4
close(ch)
}