例行检查范围

时间:2018-10-23 07:30:14

标签: go channel goroutine

我在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例程在通道上循环会导致死锁。

4 个答案:

答案 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)
}