How can I read from a channel after my GoRoutines have finished running?

huangapple go评论94阅读模式
英文:

How can I read from a channel after my GoRoutines have finished running?

问题

我目前有两个函数:pushNodes(node)和updateNodes(node)。在pushNodes函数中,我通过一个通道推送值,这些值将在updateNodes中使用。为了保存准确的通道值,我需要在开始updateNodes()之前,确保所有pushNodes的Go协程都已经完成。在Go协程执行完毕后,我如何仍然可以访问通道的值?

我不断收到"fatal error: all goroutines are asleep - deadlock!"的错误。请告诉我如何从通道中获取这些值。是否有更好/替代的方法来实现这个?

// pushNodes是一个将节点值推送到通道的函数
func pushNodes(node Node) {
    defer wg.Done()
    fmt.Printf("Pushing: %d \n", node.number)
    // 选择一个随机的对等节点
    var randomnode int = rand.Intn(totalnodes)
    for randomnode == node.number {
        rand.Seed(time.Now().UnixNano())
        randomnode = rand.Intn(totalnodes)
    }
    // 如果当前节点被感染,通过通道发送值
    if node.infected {
        sentchanneldata := ChannelData{infected: true, message: node.message}
        allnodes[randomnode].channel <- sentchanneldata
        fmt.Printf("Node %d sent a value of %t and %s to node %d!\n", node.number, sentchanneldata.infected, sentchanneldata.message, allnodes[randomnode].number)
    }
}

// updateNodes是一个更新节点值的函数
func updateNodes(node Node) {
    defer wg.Done()
    fmt.Printf("Updating: %d\n", node.number)
    // 通过节点通道获取值
    receivedchanneldata := <-node.channel
    fmt.Printf("Node %d received a value of %t and %s!\n", node.number, receivedchanneldata.infected, receivedchanneldata.message)
    // 更新值
    if receivedchanneldata.infected == true {
        node.infected = true
    }
    if receivedchanneldata.message != "" {
        node.message = receivedchanneldata.message
    }
    fmt.Printf("Update successful!\n")
}

// 主函数的一部分
wg.Add(totalnodes)
for node := range allnodes {
    go pushNodes(allnodes[node])
}
wg.Wait()
fmt.Println("Infect function done!")

wg.Add(totalnodes)
for node := range allnodes {
    go updateNodes(allnodes[node])
}
wg.Wait()

以上是你提供的代码部分。

英文:

I currently have two functions pushNodes(node) and updateNodes(node). In the pushNodes function, I am pushing values through a channel that are to be used in updateNodes. In order to have accurate channel values saved, I need all pushNodes go routines to finish before starting updateNodes(). How can I still access the channel values after the GoRoutines have finished executing?

I continuously get "fatal error: all goroutines are asleep - deadlock!". Please let me know how I can get these values from the channel. Is there a better/alternate way to do this?

   //pushNodes is a function that will push the nodes values
func pushNodes(node Node) {
defer wg.Done()
fmt.Printf(&quot;Pushing: %d \n&quot;, node.number)
//Choose a random peer node
var randomnode int = rand.Intn(totalnodes)
for randomnode == node.number {
rand.Seed(time.Now().UnixNano())
randomnode = rand.Intn(totalnodes)
}
//If the current node is infected, send values through the channel
if node.infected {
sentchanneldata := ChannelData{infected: true, message: node.message}
allnodes[randomnode].channel &lt;- sentchanneldata
fmt.Printf(&quot;Node %d sent a value of %t and %s to node %d!\n&quot;, node.number, sentchanneldata.infected, sentchanneldata.message, allnodes[randomnode].number)
}
//updateNodes is a function that will update the nodes values
func updateNodes(node Node) {
defer wg.Done()
fmt.Printf(&quot;Updating: %d\n&quot;, node.number)
//get value through node channel
receivedchanneldata := &lt;-node.channel
fmt.Printf(&quot;Node %d received a value of %t and %s!\n&quot;, node.number, receivedchanneldata.infected, receivedchanneldata.message)
// update value
if receivedchanneldata.infected == true {
node.infected = true
}
if receivedchanneldata.message != &quot;&quot; {
node.message = receivedchanneldata.message
}
fmt.Printf(&quot;Update successful!\n&quot;)
}
//Part of main functions
wg.Add(totalnodes)
for node := range allnodes {
go pushNodes(allnodes[node])
}
wg.Wait()
fmt.Println(&quot;Infect function done!&quot;)
wg.Add(totalnodes)
for node := range allnodes {
go updateNodes(allnodes[node])
}
wg.Wait()

答案1

得分: 1

在Go语言中,通道(channel)的存在以及其中的数据与使用它的goroutine是独立的,只要至少还有一个goroutine可以读取或写入通道,通道就会继续存在(直到所有相关的goroutine都结束后,通道最终会被垃圾回收)。

根据你提供的代码示例无法使用(正如已经指出的),所以我们无法确定你的问题出在哪里。但是,如果你尝试在最后一个可运行的goroutine中从通道中读取数据,并且这个goroutine会因为等待通道上的消息而进入休眠状态,而其他的Go运行时可以确定当前休眠的goroutine将永远不会被唤醒并从通道中接收到消息时,你将会得到类似于你报告的fatal错误消息:

> fatal error: all goroutines are asleep - deadlock!

例如,假设你有7个正在运行的goroutine,其中一个达到以下代码行:

msg = &lt;-ch

其中ch是一个当前没有可用数据的打开通道。其中一个goroutine到达这行代码后会阻塞(“进入休眠”),等待剩下的六个goroutine执行以下操作之一:

ch &lt;- whatever

这将唤醒第7个goroutine。因此,现在只剩下6个goroutine可以在ch上写入数据或关闭ch。如果这剩下的六个goroutine通过相同的行,一个接一个地或同时通过,而它们中没有一个发送数据到通道或关闭通道,那么这些剩下的goroutine也会被阻塞。当最后一个goroutine被阻塞时,运行时将意识到程序陷入了死锁,并发生panic。

然而,如果只有剩下的六个goroutine中的五个像这样被阻塞,然后第六个goroutine执行以下代码:

close(ch)

这个close操作将关闭通道,导致这六个“陷入休眠”的goroutine接收到以零值表示的“数据结束”的“假”消息msg。你也可以使用接收操作的两个返回值形式:

msg, ok = &lt;-ch

这里,如果通道没有关闭,ok将得到truemsg包含实际的消息;如果通道已经关闭,ok将得到falsemsg将包含一个零值的“假”消息。

因此,你可以选择:

  • 关闭通道以表示你不打算再发送任何数据,或者
  • 仔细匹配“从通道接收”的操作次数与“在通道上发送消息”的操作次数。

前一种情况在无法预先知道应该发送多少消息到通道的情况下是常见的。即使你知道应该发送多少消息,也可以使用这种方式。一个典型的构造如下:

ch := make(chan T)  // T是某种类型
// 进行适当的其他设置
var wg sync.WaitGroup
wg.Add(N)  // N是某个数字
// 启动N个goroutine,每个goroutine可能在通道上发送任意数量的消息
for i := 0; i &lt; N; i++ {
go doSomething(&amp;wg, ch)
// 在doSomething中,在发送完ch上的消息后调用wg.Done()
}
go func() {
wg.Wait()  // 等待所有N个goroutine完成
close(ch)  // 然后,关闭通道
}()
// 在这里启动从通道接收的函数,可以是内联的方式或者更多的goroutine;当它们看到通道已关闭时,它们就会结束。

这种模式依赖于能够创建一个额外的第N+1个goroutine(即匿名函数go func() { ... }()序列),它的唯一任务是等待所有发送者都说“我已经发送完了”。每个发送者通过调用wg.Done()来完成这个任务。这样,没有任何一个发送者有特殊的责任来关闭通道:它们只是写入数据,然后在完成写入后宣布“我已经写完了”。只有一个goroutine有一个特殊的责任:它等待所有发送者都宣布“我已经写完了”,然后关闭通道并退出,完成它的唯一任务。

所有的接收者(无论是一个还是多个)现在很容易知道什么时候没有人会再发送任何东西了,因为它们在这一点上看到了一个已关闭的通道。因此,如果大部分工作都在发送方,你甚至可以在这里使用主goroutine,使用一个简单的for ... range ch循环。

英文:

> How can I still access the channel values after the GoRoutines have finished executing?

A channel's existence, including any data that have been shoved into it, is independent of the goroutines that might read from or write to it, provided that at least one goroutine still exists that can read from and/or write to it. (Once all such goroutines are gone, the channel will—eventually—be GC'ed.)

Your code sample is unusable (as already noted) so we can't say precisely where you have gone wrong, but you'll get the kind of fatal message you report here:

> fatal error: all goroutines are asleep - deadlock!

if you attempt to read from a channel in the last runnable goroutine, such that this goroutine goes to sleep to await a message on that channel, in such a way that the rest of the Go runtime can determine for certain that no currently-asleep goroutine will ever wake up and deliver a message on that channel. For instance, suppose you have 7 total goroutines running right as one of them reaches the following line of code:

msg = &lt;-ch

where ch is an open channel with no data available right now. One of those 7 goroutines reaches this line and blocks ("goes to sleep"), waiting for one of the remaining six goroutines to do:

ch &lt;- whatever

which would wake up that 7th goroutine. So now there are only 6 goroutines that can write on ch or close ch. If those six remaining goroutines also pass through the same line, one at a time or several or all at once, with none of them ever sending on the channel or closing it, those remaining goroutines will also block. When the last one of them blocks, the runtime will realize that the program is stuck, and panic.

If, however, only five of the remaining six goroutines block like this, and then the sixth one runs though a line reading:

close(ch)

that close operation will close the channel, causing all six of the "stuck asleep" goroutines to receive "end of data" represented by a zero-valued "fake" message msg. You can also use the two-valued form of receive:

msg, ok = &lt;-ch

Here ok gets true if the channel isn't closed and msg contains a real message, but gets false if the channel is closed and msg now contains a zero-valued "fake" message.

Thus, you can either:

  • close the channel to indicate that you plan not to send anything else, or
  • carefully match up the number of "receive from channel" operations to the number of "send message on channel" operations.

The former is the norm with channels where there's no way to know in advance how many messages should be sent on the channel. It can still be used even if you do know. A typical construct for doing the close is:

ch := make(chan T)  // for some type T
// do any other setup that is appropriate
var wg sync.WaitGroup
wg.add(N)  // for some number N
// spin off some number of goroutines N, each of which may send
// any number of messages on the channel
for i := 0; i &lt; N; i++ {
go doSomething(&amp;wg, ch)
// in doSomething, call wg.Done() when done sending on ch
}
go func() {
wg.Wait()  // wait for all N goroutines to finish
close(ch)  // then, close the channel
}()
// Start function(s) that receive from the channel, either
// inline or in more goroutines here; have them finish when
// they see that the channel is closed.

This pattern relies on the ability to create an extra N+1'th goroutine—that's the anonymous function go func() { ... }() sequence—whose entire job in life is to wait for all the senders to say I am done sending. Each sender does that by calling wg.Done() once. That way, no sender has any special responsibility for closing the channel: they all just write and then announce "I'm done writing" when they are done writing. One goroutine has one special responsibility: it waits for all senders to have announced "I'm done writing", and then it closes the channel and exits, having finished its one job in life.

All receivers—whether that's one or many—now have an easy time knowing when nobody will ever send anything any more, because they see a closed channel at that point. So if most of the work is on the sending side, you can even use the main goroutine here with a simple for ... range ch loop.

huangapple
  • 本文由 发表于 2021年10月1日 09:05:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/69400242.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定