下面的代码可以很好地处理硬编码的JSON数据,但是当我从文件中读取JSON数据时就不行了.当我使用sync.WaitGroup时,我得到了fatal error: all goroutines are asleep - deadlock个错误.

WORKING EXAMPLE WITH HARD-CODED JSON DATA:

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)
}

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

NOT WORKING - EXAMPLE WITH READING JSON DATA FILE:

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()
}

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$

HOSTS.TXT

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

推荐答案

Go program ends when the main function ends.

language specification

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

因此,您需要等待您的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")
}

以及同步多个goroutine

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.这必须在main goroutine中完成,以防止程序退出.

*实际参数为evaluated before starting new gouroutine.因此,需要在wg.Add(1)之前对它们进行显式求值,以便可能出现panic 的代码不会留下增加的计数器.

使用

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

而不是

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

Go相关问答推荐

SEARCH On Conflict Clause不考虑乐观锁定版本

追加一个字节数组的分配比2个字节数组的分配要少得多

迭代字符串并用映射值替换原始字符串中的值的惯用方法

当客户端同时是服务器时,需要什么 mTLS 证书?

如何使用管理员权限启动 powershell 进程并重定向标准输入 (os.exec)

是否可以从 golang 中的参数推断类型?

Go 切片容量增长率

如何在模板中传递和访问 struct 片段和 struct

如何确定作为函数参数传递的指针是否正在被修改或副本是否正在被修改?

获取不带类型参数的泛型 struct 的类型名称

枚举的 Golang 验证器自定义验证规则

如何为导入的嵌入式 struct 文字提供值?

从动态输入中提取字符串,其中部分字符串可能不存在

Golang invopop jsonschema 使用 if/then/else

如何使用 Status 字段创建 Kubernetes 对象?

go 是否对 struct 使用空间填充之类的东西?

Golang prometheus 显示自定义指标

不理解切片和指针

在 go (1.18) 的泛型上实现多态的最佳方法是什么?

更改单个像素的 colored颜色 - Golang 图像