【问题标题】:Send value through channel to multiple go routines通过通道将值发送到多个 goroutine
【发布时间】:2018-09-29 11:26:57
【问题描述】:

我想在通道中发送一个值以从主函数执行例程。发生的情况是哪个 goroutine 将首先从通道接收值。

package main

import (
    "fmt"
    "math/rand"
    //"runtime"
    "strconv"
    "time"
)

func main() {
    var ch chan int
    ch = make(chan int)
    ch <- 1
    receive(ch)
}

func receive(ch chan int){
    for i := 0; i < 4; i++ {
        // Create some threads
        go func(i int) {
            time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
            fmt.Println(<-ch)
        }(i)
    }
}

我当前的实现出错了。

致命错误:所有 goroutine 都处于休眠状态 - 死锁!

我怎么知道哪个 goroutine 将首先从通道接收值。其他 goroutine 会发生什么如果因为没有通道接收值而运行或抛出错误。因为其中一个已经收到了。

如果创建缓冲通道,我的代码可以工作。所以我不明白幕后发生了什么,这使它在创建如下所示的缓冲通道时起作用:

func main() {
    var ch chan int
    ch = make(chan int, 10)
    ch <- 1
    receive(ch)
}

如果我们看下面的代码。我可以看到我们可以直接通过通道发送值,不需要创建一个 goroutine 来通过一个通道向另一个 goroutine 发送一个值。

package main

import "fmt"

func main() {

    // We'll iterate over 2 values in the `queue` channel.
    queue := make(chan string, 2)
    queue <- "one"
    queue <- "two"
    close(queue)

    for elem := range queue {
        fmt.Println(elem)
    }
}

那么我的代码有什么问题。为什么会造成死锁。

【问题讨论】:

  • ch &lt;- 1 阻塞直到&lt;- creceive 永远不会被执行。
  • 即使在编辑之后你仍然有同样的问题。
  • 一种选择是为每个你想要发送值的 goroutine 创建一个通道。我不知道这是否是最好的选择。
  • 阅读我的第一条评论,这就是您收到错误的原因。 receive 永远不会被调用。要解决此问题,您可以在其自己的 goroutine 中发送到通道。例如。 go func() { ch&lt;-1 }().
  • 顺序未指定,取决于调度程序的实现或在 Go 中处理 goroutine 的任何东西。这意味着来自第一个循环迭代的 goroutine 不一定是第一个被执行的。

标签: go


【解决方案1】:

如果您只需要启动多个工作人员并向其中任何个工作人员发送任务,那么您最好在向通道发送值之前运行工作人员,因为正如@mkopriva 上面所说,写入通道是一个阻塞操作。您始终必须有一个消费者,否则执行将冻结。

func main() {
    var ch chan int
    ch = make(chan int)

    receive(ch)

    ch <- 1
}

func receive(ch chan int) {
    for i := 0; i < 4; i++ {
        // Create some threads
        go func(i int) {
            time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
            fmt.Printf("Worker no %d is processing the value %d\n", i, <-ch)
        }(i)
    }
}

对于“哪个 goroutine 会收到它?”这个问题的简短回答- 随便。 :) 其中任何一个,你都不能确定。

但是我不知道什么是 time.Sleep(...) 在那里,保持原样。

【讨论】:

    【解决方案2】:

    一个无缓冲的通道(没有长度)阻塞,直到接收到值。这意味着写入通道的程序将在写入通道后停止,直到它被读取为止。如果这发生在主线程中,在您调用 receive 之前,它会导致死锁。

    还有两个问题:您需要使用WaitGroup 来暂停完成直到完成,并且通道的行为类似于并发队列。特别是它有 push 和 pop 操作,这两个操作都是使用&lt;- 执行的。例如:

    //Push to channel, channel contains 1 unless other things were there
    c <- 1
    //Pop from channel, channel is empty
    x := <-c
    

    这是一个工作示例:

    package main
    
    import (
            "fmt"
            "math/rand"
            "sync"
            "time"
    )
    
    func main() {
            var ch chan int 
            ch = make(chan int)
            go func() {
                    ch <- 1
                    ch <- 1
                    ch <- 1
                    ch <- 1
            }() 
            receive(ch)
    }
    
    func receive(ch chan int) {
            wg := &sync.WaitGroup{}
            for i := 0; i < 4; i++ {
                    // Create some threads
                    wg.Add(1)
                    go func(i int) {
                            time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
                            fmt.Println(<-ch)
                            wg.Done()
                    }(i)
            }   
            wg.Wait()
            fmt.Println("done waiting")
    }
    

    Playground Link

    如您所见,WaitGroup 也很简单。您在更高的范围内声明它。它本质上是一个花哨的计数器,具有三种主要方法。当你调用wg.Add(1)时计数器增加,当你调用wg.Done()时计数器减少,当你调用wg.Wait()时停止执行直到计数器达到0。

    【讨论】:

    • 谢谢,但如果你看看这个play.golang.org/p/EoKZu_iTTzK。没有强制只使用带有 go 例程的通道。 goroutine 也是一个运行在不同线程上的函数。问题是别的
    • 好一个。现在你自己进入一个问题。这就是我想知道的,为什么它不能使用通道在 go 例程之外发送值。虽然这是可能的。但是有人拒绝了我的问题,不知道为什么。
    • 这个答案解释了它:stackoverflow.com/a/18660709/1318734“如果通道没有缓冲,发送方会阻塞,直到接收方收到该值。”因此,当主线程正在等待某些无法启动的事情发生时,这会导致一些问题,因为它会在同一个线程中发生(在本例中为 receive)。
    • @Himanshu 一个 goroutine 连同它的作用域大概会占用一些内存,不管它可能有多小。因此,我相信泄漏 goroutine 等同于泄漏内存。不是你想要的。关闭频道即可。
    • 如果您尝试从没有值的通道读取,它将阻塞,直到有其他内容写入它为止。如果没有其他 goroutine 在运行,也会导致死锁。例如:play.golang.org/p/NjCYU1kpbtP 但是在这里 (play.golang.org/p/DRhHJ7LP2ci) 我们还有其他的 goroutines 仍然活着,所以没有死锁。请注意,在其他人完成睡眠之前,他们不会写入频道,因为他们睡得更久。从它读取的那些只是阻塞 他们自己的执行,直到值被写入。
    猜你喜欢
    • 2013-03-20
    • 2017-09-20
    • 1970-01-01
    • 2020-08-02
    • 1970-01-01
    • 2022-08-17
    • 2018-10-23
    • 1970-01-01
    • 2020-06-14
    相关资源
    最近更新 更多