【问题标题】:GO language: fatal error: all goroutines are asleep - deadlockGO 语言:致命错误:所有 goroutine 都处于休眠状态 - 死锁
【发布时间】:2015-01-11 16:42:31
【问题描述】:

下面的代码适用于硬编码的 JSON 数据,但是当我从文件中读取 JSON 数据时不起作用。使用sync.WaitGroup 时出现fatal error: all goroutines are asleep - deadlock 错误。

使用硬编码 JSON 数据的工作示例:

package main

import (
    "bytes"
    "fmt"
    "os/exec"
    "time"
)

func connect(host string) {
    cmd := exec.Command("ssh", host, "uptime")
    var out bytes.Buffer
    cmd.Stdout = &out
    err := cmd.Run()
    if err != nil {
        fmt.Println(err)
    }
    fmt.Printf("%s: %q\n", host, out.String())
    time.Sleep(time.Second * 2)
    fmt.Printf("%s: DONE\n", host)
}

func listener(c chan string) {
    for {
        host := <-c
        go connect(host)
    }
}

func main() {
    hosts := [2]string{"user1@111.79.154.111", "user2@111.79.190.222"}
    var c chan string = make(chan string)
    go listener(c)

    for i := 0; i < len(hosts); i++ {
        c <- hosts[i]
    }
    var input string
    fmt.Scanln(&input)
}

输出:

user@user-VirtualBox:~/go$ go run channel.go
user1@111.79.154.111: " 09:46:40 up 86 days, 18:16,  0 users,  load average: 5"
user2@111.79.190.222: " 09:46:40 up 86 days, 17:27,  1 user,  load average: 9"
user1@111.79.154.111: DONE
user2@111.79.190.222: DONE

不起作用 - 读取 JSON 数据文件的示例:

package main

import (
    "bytes"
    "fmt"
    "os/exec"
    "time"
    "encoding/json"
    "os"
    "sync"
)

func connect(host string) {
    cmd := exec.Command("ssh", host, "uptime")
    var out bytes.Buffer
    cmd.Stdout = &out
    err := cmd.Run()
    if err != nil {
        fmt.Println(err)
    }
    fmt.Printf("%s: %q\n", host, out.String())
    time.Sleep(time.Second * 2)
    fmt.Printf("%s: DONE\n", host)
}

func listener(c chan string) {
    for {
        host := <-c
        go connect(host)
    }
}

type Content struct {
    Username string `json:"username"`
    Ip       string `json:"ip"`
}

func main() {
    var wg sync.WaitGroup

    var source []Content
    var hosts []string
    data := json.NewDecoder(os.Stdin)
    data.Decode(&source)

    for _, value := range source {
        hosts = append(hosts, value.Username + "@" + value.Ip)
    }

    var c chan string = make(chan string)
    go listener(c)

    for i := 0; i < len(hosts); i++ {
        wg.Add(1)
        c <- hosts[i]
        defer wg.Done()
    }

    var input string
    fmt.Scanln(&input)

    wg.Wait()
}

输出

user@user-VirtualBox:~/go$ go run deploy.go < hosts.txt 
user1@111.79.154.111: " 09:46:40 up 86 days, 18:16,  0 users,  load average: 5"
user2@111.79.190.222: " 09:46:40 up 86 days, 17:27,  1 user,  load average: 9"
user1@111.79.154.111 : DONE
user2@111.79.190.222: DONE
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc210000068)
    /usr/lib/go/src/pkg/runtime/sema.goc:199 +0x30
sync.(*WaitGroup).Wait(0xc210047020)
    /usr/lib/go/src/pkg/sync/waitgroup.go:127 +0x14b
main.main()
    /home/user/go/deploy.go:64 +0x45a

goroutine 3 [chan receive]:
main.listener(0xc210038060)
    /home/user/go/deploy.go:28 +0x30
created by main.main
    /home/user/go/deploy.go:53 +0x30b
exit status 2
user@user-VirtualBox:~/go$

HOSTS.TXT

[
   {
      "username":"user1",
      "ip":"111.79.154.111"
   },
   {
      "username":"user2",
      "ip":"111.79.190.222"
   }
]

【问题讨论】:

    标签: go


    【解决方案1】:

    Go 程序在 main 函数结束时结束。

    来自language specification

    程序执行从初始化主包开始,然后调用函数 main。当该函数调用返回时,程序退出。它不会等待其他(非主)goroutine 完成。

    因此,您需要等待 goroutine 完成。常见的解决方案是使用sync.WaitGroup 对象。

    同步goroutine的最简单代码:

    package main
    
    import "fmt"
    import "sync"
    
    var wg sync.WaitGroup // 1
    
    func routine() {
        defer wg.Done() // 3
        fmt.Println("routine finished")
    }
    
    func main() {
        wg.Add(1) // 2
        go routine() // *
        wg.Wait() // 4
        fmt.Println("main finished")
    }
    

    用于同步多个 goroutines

    package main
    
    import "fmt"
    import "sync"
    
    var wg sync.WaitGroup // 1
    
    func routine(i int) {
        defer wg.Done() // 3
        fmt.Printf("routine %v finished\n", i)
    }
    
    func main() {
        for i := 0; i < 10; i++ {
            wg.Add(1) // 2
            go routine(i) // *
        }
        wg.Wait() // 4
        fmt.Println("main finished")
    }
    

    WaitGroup 按执行顺序使用。

    1. 全局变量声明。使其全局化是使其对所有函数和方法可见的最简单方法。
    2. 增加计数器。这必须在 main goroutine 中完成,因为由于内存模型guarantees,无法保证新启动的 goroutine 将在 4 之前执行。
    3. 减少计数器。这必须在 goroutine 的出口处完成。使用延迟调用,我们确保不管它如何结束,它都会be called whenever function ends
    4. 等待计数器达到 0。这必须在主 goroutine 中完成以防止程序退出。

    * 实际参数为evaluated before starting new gouroutine。因此需要在wg.Add(1) 之前明确评估它们,这样可能出现的恐慌代码就不会留下增加的计数器。

    使用

    param := f(x)
    wg.Add(1)
    go g(param)
    

    而不是

    wg.Add(1)
    go g(f(x))
    

    【讨论】:

    • 我从昨天开始(甚至不到一天)就一直在使用 GO,所以虽然我一直在阅读文档,但我并不是很了解。上面的代码是 1 天学习/练习的结果。我究竟把睡眠放在哪里?我研究了这个例子。
    • @inanzzz 看第一个答案。
    • 我想我把东西放在了错误的地方。 fatal error: all goroutines are asleep - deadlock!
    • 伙计们我更新了上面的帖子,因为至少有一个进展但是这次有一个deadlock。我移动了wg 线,但找不到正确的位置。
    • @inanzzz 更新帖子并不是你能做的最好的事情。现在答案没有意义,不再可用。是的,wg.Done() 调用应该在 goroutines 中。
    【解决方案2】:

    感谢 Grzegorz Żur 提供的非常好的和详细的解释。 我想指出的一件事是,通常需要线程化的函数不会在main() 中,所以我们会有这样的东西:

    package main
    
    import (
        "bufio"
        "fmt"
        "io"
        "io/ioutil"
        "math/rand"
        "os"
        "reflect"
        "regexp"
        "strings"
        "sync"
        "time"
    )
    
    var wg sync.WaitGroup    // VERY IMP to declare this globally, other wise one   //would hit "fatal error: all goroutines are asleep - deadlock!"
    
    func doSomething(arg1 arg1Type) {
         // cured cancer
    }
    
    func main() {
        r := rand.New(rand.NewSource(time.Now().UnixNano()))
        randTime := r.Intn(10)
        wg.Add(1)    
        go doSomething(randTime)
        wg.Wait()
        fmt.Println("Waiting for all threads to finish")
    }
    

    我要指出的是,wg 的全局声明对于所有线程在main() 之前完成非常重要

    【讨论】:

    • 不一定。你只需要确保你传递一个指针,这样你就不会得到副本...wg := &amp;sync.WaitGroup{} 带有函数:func goDo(..., wg *sync.WaitGroup)
    【解决方案3】:

    试试这个代码片段

    package main
    
    import (
        "bytes"
        "fmt"
        "os/exec"
        "time"
        "sync"
    )
    
    func connect(host string, wg *sync.WaitGroup) {
        defer wg.Done()
        cmd := exec.Command("ssh", host, "uptime")
        var out bytes.Buffer
        cmd.Stdout = &out
        err := cmd.Run()
        if err != nil {
            fmt.Println(err)
        }
        fmt.Printf("%s: %q\n", host, out.String())
        time.Sleep(time.Second * 2)
        fmt.Printf("%s: DONE\n", host)
    }
    
    func listener(c chan string,wg *sync.WaitGroup) {
        for {
            host,ok := <-c
            // check channel is closed or not
            if !ok{
                break
            }
            go connect(host)
        }
    
    }
    
    func main() {
        var wg sync.WaitGroup
        hosts := [2]string{"user1@111.79.154.111", "user2@111.79.190.222"}
        var c chan string = make(chan string)
        go listener(c)
    
        for i := 0; i < len(hosts); i++ {
            wg.Add(1)
            c <- hosts[i]
        }
        close(c)
        var input string
        fmt.Scanln(&input)
        wg.Wait()
    }
    

    【讨论】:

    • 嗨,欢迎来到 Stack Overflow。请考虑在您的答案中添加更多上下文,而不是简单地发布可能会或可能不会解决问题的代码 sn-p。
    猜你喜欢
    • 1970-01-01
    • 2015-04-02
    • 1970-01-01
    • 2013-11-22
    • 1970-01-01
    • 2012-09-06
    • 2022-01-21
    • 2016-07-30
    • 2021-04-05
    相关资源
    最近更新 更多