通过通道将值发送到多个 goroutine

Posted

技术标签:

【中文标题】通过通道将值发送到多个 goroutine【英文标题】:Send value through channel to multiple go routines 【发布时间】: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 不一定是第一个被执行的。 【参考方案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 例程之外发送值。虽然这是可能的。但是有人拒绝了我的问题,不知道为什么。 这个答案解释了它:***.com/a/18660709/1318734“如果通道没有缓冲,发送方会阻塞,直到接收方收到该值。”因此,当主线程正在等待某些无法启动的事情发生时,这会导致一些问题,因为它会在同一个线程中发生(在本例中为 receive)。 @Himanshu 一个 goroutine 连同它的作用域大概会占用一些内存,不管它可能有多小。因此,我相信泄漏 goroutine 等同于泄漏内存。不是你想要的。关闭频道即可。 如果您尝试从没有值的通道读取,它将阻塞,直到有其他内容写入它为止。如果没有其他 goroutine 在运行,也会导致死锁。例如:play.golang.org/p/NjCYU1kpbtP 但是在这里 (play.golang.org/p/DRhHJ7LP2ci) 我们还有其他的 goroutines 仍然活着,所以没有死锁。请注意,在其他人完成睡眠之前,他们不会写入频道,因为他们睡得更久。从它读取的那些只是阻塞 他们自己的执行,直到值被写入。

以上是关于通过通道将值发送到多个 goroutine的主要内容,如果未能解决你的问题,请参考以下文章

Prometheus alertmanager 向多个 slack 通道发送通知

023_go语言中的通道

通过 websocket 或使用 WebRTC 的数据通道逐个字符发送?

如何通过 ajax 将值从 django 模板发送到视图?

如何将机器人消息发送到一台服务器中的多个通道(阅读正文以获取更多信息)

使用通道作为队列的死锁