GO语言:致命错误:所有goroutines都睡着了 - 僵局

Ben*_*der 21 go

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

使用硬编码的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)
}
Run Code Online (Sandbox Code Playgroud)

OUTPUT:

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
Run Code Online (Sandbox Code Playgroud)

不工作 - 阅读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()
}
Run Code Online (Sandbox Code Playgroud)

OUTPUT

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$
Run Code Online (Sandbox Code Playgroud)

HOSTS.TXT

[
   {
      "username":"user1",
      "ip":"111.79.154.111"
   },
   {
      "username":"user2",
      "ip":"111.79.190.222"
   }
]
Run Code Online (Sandbox Code Playgroud)

Grz*_*Żur 44

当主函数结束时,Go程序结束.

语言规范

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

因此,您需要等待您的goroutines完成.对此的常见解决方案是使用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")
}
Run Code Online (Sandbox Code Playgroud)

并用于同步多个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")
}
Run Code Online (Sandbox Code Playgroud)

WaitGroup按执行顺序使用.

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

*在开始新的gouroutine之前评估实际参数.因此,需要明确地对它们进行评估,wg.Add(1)以便可能恐慌的代码不会留下增加的计数器.

使用

param := f(x)
wg.Add(1)
go g(param)
Run Code Online (Sandbox Code Playgroud)

代替

wg.Add(1)
go g(f(x))
Run Code Online (Sandbox Code Playgroud)


小智 5

感谢 Grzegorz \xc5\xbbur 提供的非常详细的解释。\n我想指出的是,通常需要线程化的 func 不会在 中main(),所以我们会有这样的东西:

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

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

\n

  • 不必要。您只需要确保传递一个指针,这样就不会得到副本...`wg := &amp;sync.WaitGroup{}` 以及函数:`func goDo(..., wg *sync.WaitGroup)`。 (3认同)