1. ホーム
  2. go

[解決済み] GO言語:致命的なエラー:すべてのゴルーチンがスリープしている - デッドロック

2022-02-01 06:01:37

質問

以下のコードは、ハードコードされた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{"[email protected]", "[email protected]"}
    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
[email protected]: " 09:46:40 up 86 days, 18:16,  0 users,  load average: 5"
[email protected]: " 09:46:40 up 86 days, 17:27,  1 user,  load average: 9"
[email protected]: DONE
[email protected]: 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()
}

OUTPUT

user@user-VirtualBox:~/go$ go run deploy.go < hosts.txt 
[email protected]: " 09:46:40 up 86 days, 18:16,  0 users,  load average: 5"
[email protected]: " 09:46:40 up 86 days, 17:27,  1 user,  load average: 9"
[email protected] : DONE
[email protected]: 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のプログラムはmain関数が終了すると終了します。

より 言語仕様

プログラムの実行は、main パッケージの初期化から始まり、main 関数を呼び出す。この関数の呼び出しが戻ると、プログラムは終了します。他の(main 以外の)ゴルーチンの完了を待つことはありません。

したがって、ゴルーチンが終了するのを待つ必要があります。これに対する一般的な解決策は sync.WaitGroup オブジェクトを作成します。

ゴルーチンを同期させるための最も単純なコードです。

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

また、複数のゴルーチンを同期させるために

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. カウンターを増加させる。メモリモデルの関係で、新しく起動したゴルーチンが4より前に実行される保証はないので、これはメインゴルーチンの中で行う必要がある ギャランティー .
  3. カウンターをデクリメントする。これはgoroutineの終了時に行う必要があります。遅延呼び出しを使って、確実に 関数が終了するたびに呼び出される が、どのように終了しようとも、関係ありません。
  4. カウンタが0になるのを待つ。プログラムの終了を防ぐため、メインゴルーチンの中で行わなければならない。

* 実際のパラメータは 新しいgouroutineを開始する前に評価されます。 . そのため,以下のように明示的に評価する必要があります. wg.Add(1) そのため、パニックを起こす可能性のあるコードがカウンターを増やさないようにするためです。

使用する

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

ではなく

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