通过通道将值发送到多个 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 <- 1
阻塞直到<- c
,receive
永远不会被执行。
即使在编辑之后你仍然有同样的问题。
一种选择是为每个你想要发送值的 goroutine 创建一个通道。我不知道这是否是最好的选择。
阅读我的第一条评论,这就是您收到错误的原因。 receive
永远不会被调用。要解决此问题,您可以在其自己的 goroutine 中发送到通道。例如。 go func() ch<-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 操作,这两个操作都是使用<-
执行的。例如:
//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 通道发送通知
通过 websocket 或使用 WebRTC 的数据通道逐个字符发送?