【问题标题】:fatal error: all goroutines are asleep - deadlock | Go Routine致命错误:所有 goroutines 都睡着了 - 死锁 |去例行公事
【发布时间】:2023-02-01 13:39:54
【问题描述】:

问题在于 goOne 和 goTwo 函数都分别向通道 ch1 和 ch2 发送值,但是在 main 函数中没有对应这些值的接收器。这意味着通道被阻塞,程序无法继续。导致main函数中的select语句无法从channels中读取,所以一直执行default case。

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    ch1 := make(chan string)
    ch2 := make(chan string)

    wg.Add(2)
    go goOne(&wg, ch1)
    go goTwo(&wg, ch2)

    select {
    case <-ch1:
        fmt.Println(<-ch1)
        close(ch1)

    case <-ch2:
        fmt.Println(<-ch2)
        close(ch2)

    default:
        fmt.Println("Default Case")
    }
    wg.Wait()

}

func goTwo(wg *sync.WaitGroup, ch2 chan string) {
    ch2 <- "Channel 2"
    wg.Done()
}

func goOne(wg *sync.WaitGroup, ch1 chan string) {
    ch1 <- "Channel 1"
    wg.Done()
}

输出:

Default Case
fatal error: all goroutines are asleep - deadlock!

goroutine 1 \[semacquire\]:
sync.runtime_Semacquire(0xc000108270?)
/usr/local/go/src/runtime/sema.go:62 +0x25
sync.(\*WaitGroup).Wait(0x4b9778?)
/usr/local/go/src/sync/waitgroup.go:139 +0x52
main.main()
/home/nidhey/Documents/Go_Learning/goroutines/select.go:29 +0x2af

goroutine 6 \[chan send\]:
main.goOne(0x0?, 0x0?)
/home/nidhey/Documents/Go_Learning/goroutines/select.go:39 +0x28
created by main.main
/home/nidhey/Documents/Go_Learning/goroutines/select.go:14 +0xc5

goroutine 7 \[chan send\]:
main.goTwo(0x0?, 0x0?)
/home/nidhey/Documents/Go_Learning/goroutines/select.go:33 +0x28
created by main.main
/home/nidhey/Documents/Go_Learning/goroutines/select.go:15 +0x119\```

我正在寻找一种不同的模式,例如 select 来处理通道被阻塞的情况。

为了解决这个问题,我在 wg.Wait() 之后的主函数中添加了一个 <-ch1 或 <-ch2 来接收发送到通道的值并解除它们的阻塞

【问题讨论】:

  • 您需要提供更多信息,说明您希望通过代码实现什么,以及您期望发生什么。目前还不清楚你是想等待两个 goroutines 完成,还是只等待其中一个。
  • 想象一下,如果我们有两个 api 端点,API1 和 API2,它们返回相同的数据但托管在不同的区域。所以我想做的是,我需要在两个不同的函数(即 goroutines)中对两个 api 进行 API 调用,一旦任何一个 api 向我们发送响应,我就想处理接收到的数据。因此,我首先使用选择块检查 whcih api 是否正在获取数据。
  • 不能保证执行默认值,它只是在这里执行,因为此时两个通道都没有准备好接收。删除默认值以查看会发生什么(这当然是另一个死锁,因为您正试图从一个只会发送一个值的通道接收两个值)。如果你说你只是在寻找从通道接收到的第一个值,那么将该值分配给一个变量并且不要尝试再次接收。您正在丢弃第一个值。

标签: go concurrency goroutine channels waitgroup


【解决方案1】:

目前还不完全清楚你想做什么。如果你想等待两个 goroutines 完成他们的工作并将他们的工作结果放入通道,你不需要权重组(因为它不会被达到)。

你可以这样做。

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go goOne(ch1)
    go goTwo(ch2)

    for {
        select {
        case v := <-ch1:
            fmt.Println("Done ch1:", v)
            ch1 = nil
        case v := <-ch2:
            fmt.Println("Done ch2:", v)
            ch2 = nil
        case <-time.After(time.Second):
            fmt.Println("I didn't get result so lets skip it!")
            ch1, ch2 = nil, nil
        }
        if ch1 == nil && ch2 == nil {
            break
        }
    }
}

func goTwo(ch chan string) {
    ch <- "Channel 2"
}

func goOne(_ chan string) {
    //ch1 <- "Channel 1"
}

更新:

想象一下,如果我们有两个 api 端点,API1 和 API2,它们返回相同的数据但托管在不同的区域。所以我想做的是,我需要在两个不同的函数(即 goroutines)中对两个 api 进行 API 调用,一旦任何一个 api 向我们发送响应,我就想处理接收到的数据。因此,我首先使用选择块检查 whcih api 是否正在获取数据。

package main

import (
    "context"
    "fmt"
    "math/rand"
    "time"
)

func main() {

    regions := []string{
        "Europe",
        "America",
        "Asia",
    }

    // Just for different results for each run
    rand.Seed(time.Now().UnixNano())
    rand.Shuffle(len(regions), func(i, j int) { regions[i], regions[j] = regions[j], regions[i] })

    output := make(chan string)

    ctx, cancel := context.WithTimeout(context.Background(), 2 * time.Second)
    for i, region := range regions {
        go func(ctx context.Context, region string, output chan <- string, i int) {

            // Do call with context
            // If context will be cancelled just ignore it here

            timeout := time.Duration(i)*time.Second
            fmt.Printf("Call %s (with timeout %s)
", region, timeout)
            time.Sleep(timeout) // Simulate request timeout

            select {
            case <-ctx.Done():
                fmt.Println("Cancel by context:", region)
            case output <- fmt.Sprintf("Answer from `%s`", region):
                fmt.Println("Done:", region)
            }
        }(ctx, region, output, i)
    }

    select {
    case v := <-output:
        cancel() // Cancel all requests in progress (if possible)
        // Do not close output chan to avoid panics: When the channel is no longer used, it will be garbage collected.
        fmt.Println("Result:", v)
    case <-ctx.Done():
        fmt.Println("Timeout by context done")
    }

    fmt.Println("There is we already have result or timeout, but wait a little bit to check all is okay")
    time.Sleep(5*time.Second)
}

【讨论】:

    【解决方案2】:

    首先,你有一个竞争条件,因为你的频道发布 goroutines 在你输入 select 语句时可能还没有启动,它会立即进入默认状态。

    但是假设您解决了这个问题(例如,使用另一种形式的信号量),您将进入下一个问题 - 您的 select 语句将获得 chan1 或 chan2 消息,然后在方法的底部等待,但由于它不再在select 语句,您的一条消息将不会到达,您将永远等待。

    您将需要一个循环(在本例中为两次)或一个接收器,用于在各自的 goroutine 中运行的两个通道,以获取这两个消息并完成等待组。

    但无论如何 - 正如其他人所质疑的那样 - 你想要达到什么目的?

    【讨论】:

      【解决方案3】:

      您可以尝试在单个通道上迭代(假设两个通道返回相同类型的数据),然后在 main 方法中计算完成了多少任务。然后在所有任务完成后关闭通道。例子:

      package main
      
      import (
          "fmt"
      )
      
      func main() {
          ch := make(chan string)
      
          go goOne(ch)
          go goTwo(ch)
          doneCount := 0
      
          for v := range ch {
              fmt.Println(v)
              doneCount++
              if doneCount == 2 {
                  close(ch)
              }
          }
      }
      
      func goTwo(ch chan string) {
          ch <- "Channel 2"
      }
      
      func goOne(ch chan string) {
          ch <- "Channel 1"
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-04-06
        • 2020-07-16
        • 1970-01-01
        • 1970-01-01
        • 2012-01-06
        • 2016-07-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多