常规处理频道范围

我在 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 routine 的情况下使用 range 循环通道会导致死锁。


慕虎7371278
浏览 126回答 4
4回答

隔江千里

sq这种情况是函数的输出通道没有缓冲造成的。所以sq等待下一个函数从输出中读取,但如果sq不是异步的,它就不会发生:package mainimport (    "fmt"    "sync")var wg sync.WaitGroupfunc 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}

梦里花落0921

你的函数创建一个通道,写入它,然后返回它。写入将阻塞,直到有人可以读取相应的值,但这是不可能的,因为此函数之外还没有人拥有通道。func sq(in <-chan int) <-chan int {&nbsp; &nbsp; // Nobody else has this channel yet...&nbsp; &nbsp; out := make(chan int)&nbsp; &nbsp; for n := range in {&nbsp; &nbsp; &nbsp; &nbsp; // ...but this line will block until somebody reads the value...&nbsp; &nbsp; &nbsp; &nbsp; out <- n * n&nbsp; &nbsp; }&nbsp; &nbsp; close(out)&nbsp; &nbsp; // ...and nobody else can possibly read it until after this return.&nbsp; &nbsp; return out}如果将循环包装在 goroutine 中,则允许循环和函数sq继续;即使循环阻塞,return out语句仍然可以继续,最终您将能够将阅读器连接到通道。(在 goroutines 之外的通道上循环本质上没有什么坏处;你的main函数无害且正确地完成了它。)

ITMISS

代码有点复杂,我们简化一下下面的第一个 eq,没有死锁func main() {&nbsp; &nbsp; send := make(chan int)&nbsp; &nbsp; receive := make(chan int)&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; send<-3&nbsp; &nbsp; &nbsp; &nbsp; send<-4&nbsp; &nbsp; &nbsp; &nbsp; close(send)&nbsp; &nbsp; }()&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; receive<- <-send&nbsp; &nbsp; &nbsp; &nbsp; receive<- <-send&nbsp; &nbsp; &nbsp; &nbsp; close(receive)&nbsp; &nbsp; }()&nbsp; &nbsp; for v := range receive{&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(v)&nbsp; &nbsp; }}下面的第二个 eq,删除“go”有死锁func main() {&nbsp; &nbsp; send := make(chan int)&nbsp; &nbsp; receive := make(chan int)&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; send<-3&nbsp; &nbsp; &nbsp; &nbsp; send<-4&nbsp; &nbsp; &nbsp; &nbsp; close(send)&nbsp; &nbsp; }()&nbsp; &nbsp; receive<- <-send&nbsp; &nbsp; receive<- <-send&nbsp; &nbsp; close(receive)&nbsp; &nbsp; for v := range receive{&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(v)&nbsp; &nbsp; }}让我们再次简化第二个代码func main() {&nbsp; &nbsp; ch := make(chan int)&nbsp; &nbsp; ch <- 3&nbsp; &nbsp; ch <- 4&nbsp; &nbsp; close(ch)&nbsp; &nbsp; for v := range ch{&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(v)&nbsp; &nbsp; }}死锁的原因是主协程中没有等待的缓冲通道。两种解决方案// add more cap then "channel<-" timefunc main() {&nbsp; &nbsp; ch := make(chan int,2)&nbsp; &nbsp; ch <- 3&nbsp; &nbsp; ch <- 4&nbsp; &nbsp; close(ch)&nbsp; &nbsp; for v := range ch{&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(v)&nbsp; &nbsp; }}//async "<-channel"func main() {&nbsp; &nbsp; ch := make(chan int)&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; for v := range ch {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(v)&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }()&nbsp; &nbsp; ch <- 3&nbsp; &nbsp; ch <- 4&nbsp; &nbsp; close(ch)}

撒科打诨

死锁的原因是因为 main 正在等待 returnsq和 finish,而 thesq正在等待有人读取 chan 然后它可以继续。我通过删除 sq 调用层来简化您的代码,并将一个句子分成 2 个:func main() {&nbsp; &nbsp; result := sq(gen(3, 4)) // <-- block here, because sq doesn't return&nbsp; &nbsp; for n := range result {&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(n)&nbsp; &nbsp; }&nbsp; &nbsp; fmt.Println("Process completed")}func gen(nums ...int) <-chan int {&nbsp; &nbsp; out := make(chan int)&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; for _, n := range nums {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; out <- n&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; close(out)&nbsp; &nbsp; }()&nbsp; &nbsp; return out}func sq(in <-chan int) <-chan int {&nbsp; &nbsp; out := make(chan int)&nbsp; &nbsp; for n := range in {&nbsp; &nbsp; &nbsp; &nbsp; out <- n * n&nbsp; &nbsp;// <-- block here, because no one is reading from the chan&nbsp; &nbsp; }&nbsp; &nbsp; close(out)&nbsp; &nbsp; return out}在sq方法中,如果把代码放入goroutine,willsq返回,main func不会阻塞,消费结果队列,willgoroutine继续,就没有阻塞了。func main() {&nbsp; &nbsp; result := sq(gen(3, 4)) // will not blcok here, because the sq just start a goroutine and return&nbsp; &nbsp; for n := range result {&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(n)&nbsp; &nbsp; }&nbsp; &nbsp; fmt.Println("Process completed")}func gen(nums ...int) <-chan int {&nbsp; &nbsp; out := make(chan int)&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; for _, n := range nums {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; out <- n&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; close(out)&nbsp; &nbsp; }()&nbsp; &nbsp; return out}func sq(in <-chan int) <-chan int {&nbsp; &nbsp; out := make(chan int)&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; for n := range in {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; out <- n * n // will not block here, because main will continue and read the out chan&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; close(out)&nbsp; &nbsp; }()&nbsp; &nbsp; return out}
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go