-2

我目前有两个函数 pushNodes(node) 和 updateNodes(node)。在 pushNodes 函数中,我通过要在 updateNodes 中使用的通道推送值。为了保存准确的通道值,我需要在启动 updateNodes() 之前完成所有 pushNodes goroutines。在 GoRoutines 执行完成后,我怎样才能访问通道值?

我不断收到“致命错误:所有 goroutine 都在睡觉 - 死锁!”。请让我知道如何从频道中获取这些值。有没有更好/替代的方法来做到这一点?

   //pushNodes is a function that will push the nodes values
   func pushNodes(node Node) {
      defer wg.Done()
      fmt.Printf("Pushing: %d \n", 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 <- 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 is a function that will update the nodes values
  func updateNodes(node Node) {
    defer wg.Done()
    fmt.Printf("Updating: %d\n", node.number)
    //get value through node channel
    receivedchanneldata := <-node.channel
    fmt.Printf("Node %d received a value of %t and %s!\n", node.number, receivedchanneldata.infected, receivedchanneldata.message)
    // update value
    if receivedchanneldata.infected == true {
      node.infected = true
    }
    if receivedchanneldata.message != "" {
      node.message = receivedchanneldata.message
    }
    fmt.Printf("Update successful!\n")
  }

   //Part of main functions
    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()
4

1 回答 1

0

在 GoRoutines 执行完成后,我怎样才能访问通道值?

一个通道的存在,包括任何被推入其中的数据,独立于可能读取或写入它的 goroutine,前提是至少仍然存在一个可以读取和/或写入它的 goroutine。(一旦所有这些 goroutine 都消失了,通道最终会被 GC 处理。)

您的代码示例无法使用(如前所述),因此我们无法准确说明您出错的位置,但您会在fatal此处收到您报告的消息:

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

如果您尝试从最后一个可运行的 goroutine 中的通道读取,则该 goroutine 进入睡眠状态以等待该通道的消息,这样 Go 运行时的其余部分可以确定没有当前处于睡眠状态的 goroutine将永远醒来并该频道上传递消息。例如,假设您有 7 个 goroutine 正在运行,其中一个运行到以下代码行:

msg = <-ch

ch现在没有可用数据的开放通道在哪里。这 7 个 goroutine 中的一个到达这条线并阻塞(“进入睡眠”),等待其余六个goroutine 之一执行:

ch <- whatever

这将唤醒第 7 个 goroutine。所以现在只有 6 个 goroutine 可以写 onch或 close ch。如果这六个剩余的 goroutine通过同一行,一次一个或几个或一次全部,并且它们都没有在通道上发送或关闭它,那么剩余的 goroutine 也将阻塞。当其中最后一个阻塞时,运行时会意识到程序被卡住,并恐慌。

但是,如果剩下的 6 个 goroutine 中只有5个像这样阻塞,那么第 6 个 goroutine 会运行如下一行:

close(ch)

close操作将关闭通道,导致所有六个“陷入睡眠状态”的 goroutine 接收由零值“假”消息表示的“数据结束” msg。您还可以使用接收的二值形式:

msg, ok = <-ch

如果通道关闭并且包含真实消息,则此处ok获取,但如果通道关闭并且现在包含零值“假”消息,则获取。truemsgfalsemsg

因此,您可以:

  • 关闭频道以表明您不打算发送任何其他内容,或者
  • 仔细匹配“从通道接收”操作的数量与“在通道上发送消息”操作的数量。

前者是频道的常态,无法提前知道应该在频道上发送多少消息。即使您知道,它仍然可以使用。进行关闭的典型构造是:

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 < N; i++ {
    go doSomething(&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.

这种模式依赖于创建额外的第 N+1 个 goroutine(即匿名函数go func() { ... }()序列)的能力,其一生的全部工作就是等待所有发送者我发送完毕。每个发件人通过调用wg.Done()一次来做到这一点。这样,发送者对关闭通道没有任何特殊责任:他们都只是写,然后在写完后宣布“我写完了”。 一个goroutine 有一个特殊的责任:它等待所有发送者都宣布“我写完了”,然后它关闭通道并退出,完成了它一生中的一项工作。

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

于 2021-10-02T05:22:08.580 回答