LoginSignup
1
2

More than 5 years have passed since last update.

goroutineあれこれ2

Last updated at Posted at 2018-10-26

お題

前回の続き。
今回は、チャネル使ってあれこれ。
ただ、今回も浅いです。

関連記事Index

開発環境

# OS

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="17.10 (Artful Aardvark)"

# 言語

$ go version
go version go1.10 linux/amd64

実践

■2つのチャネル間でやりとり

$共通に使う関数$

ループ内でチャネル「ch」からの入力を待つ。
入力があったら指定のワード「word」を出力して100ミリ秒スリープ。
スリープ後にチャネル「ch」に(他のgoroutine(自身かもしれないけど)の処理を発火させるために)空の構造体を入力。

func sub(word string, ch chan struct{}, wg *sync.WaitGroup) {
    defer wg.Done()

    for i := 0; i < 5; i++ {
        select {
        case <-ch:
            fmt.Println(word)
            time.Sleep(100 * time.Millisecond)
            ch <- struct{}{}
        }
    }
}

No.01: 待ち合わせピンポン(失敗版)

func main() {
    wg := &sync.WaitGroup{}

    ch := make(chan struct{}, 1)

    wg.Add(1)
    go sub("ping", ch, wg)

    wg.Add(1)
    go sub("     pong", ch, wg)

    wg.Wait()
}

これ、失敗します。

$ go run main.go 
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc42008601c)
    /usr/lib/go-1.10/src/runtime/sema.go:56 +0x39
sync.(*WaitGroup).Wait(0xc420086010)
    /usr/lib/go-1.10/src/sync/waitgroup.go:129 +0x72
main.main()
    /work/src/golang/src/github.com/sky0621/tips-go/goroutine/no03/no0301/main.go:20 +0x11c

goroutine 18 [chan receive]:
main.sub(0x4b372b, 0x4, 0xc42007a060, 0xc420086010)
    /work/src/golang/src/github.com/sky0621/tips-go/goroutine/no03/no0301/main.go:28 +0x6c
created by main.main
    /work/src/golang/src/github.com/sky0621/tips-go/goroutine/no03/no0301/main.go:15 +0xb6

goroutine 19 [chan receive]:
main.sub(0x4b3d63, 0x9, 0xc42007a060, 0xc420086010)
    /work/src/golang/src/github.com/sky0621/tips-go/goroutine/no03/no0301/main.go:28 +0x6c
created by main.main
    /work/src/golang/src/github.com/sky0621/tips-go/goroutine/no03/no0301/main.go:18 +0x10e
exit status 2

なぜか?
メインから(チャネルへの入力待ちを行う)2つのgoroutineを起動したものの、肝心のチャネルへの入力が一切ないから(だと思う)。

ちなみに、sub関数のselectdefaultを書いてやれば↑のエラーは起きなくなる。

func sub(word string, ch chan struct{}, wg *sync.WaitGroup) {
    defer wg.Done()

    for i := 0; i < 5; i++ {
        select {
        case <-ch:
            fmt.Println(word)
            time.Sleep(100 * time.Millisecond)
            ch <- struct{}{}

ここ->        default:
ここ->            fmt.Println("skip")

        }
    }
}

すると、こうなる。

$ go run main.go 
skip
skip
skip
skip
skip
skip
skip
skip
skip
skip

この手法は、チャネルへの入力があるたびに何度でも発動する処理にしたい時に使える。
forを無限ループにしておくこととセット)

No.02: 待ち合わせピンポン(成功版)

目的は2つのgoroutine間でピンポン打ち合うことなので、No.01での失敗を修正する。
と言っても、No.01で書いた通り、2つのgoroutineが機能しだすためのきっかけをメイン関数で与えるだけ。

func main() {
    wg := &sync.WaitGroup{}

    ch := make(chan struct{}, 1)

    wg.Add(1)
    go sub("ping", ch, wg)

    wg.Add(1)
    go sub("     pong", ch, wg)

ここ->    // 最初のきっかけを与えてやる
ここ->    ch <- struct{}{}

    wg.Wait()
}

すると、2つのgoroutineが動き出す。

$ go run main.go 
     pong
ping
     pong
ping
     pong
ping
     pong
ping
     pong
ping

No.03: 待ち合わせピンポンパン

念の為、起動するgoroutineを5つにしても機能することを確認する。

func main() {
    wg := &sync.WaitGroup{}

    ch := make(chan struct{}, 1)

    words := []string{
        "ping",
        "     pong",
        "          pang",
        "               peng",
        "                    pung",
    }

    for _, word := range words {
        wg.Add(1)
        go sub(word, ch, wg)
    }

    // 最初のきっかけを与えてやる
    ch <- struct{}{}

    wg.Wait()
}

$ go run main.go 
                    pung
     pong
ping
               peng
          pang
                    pung
     pong
ping
               peng
          pang
                    pung
     pong
ping
               peng
          pang
                    pung
     pong
ping
               peng
          pang
                    pung
     pong
ping
               peng
          pang

No.04: 待ち合わせないピンポンパン

「待ち合わせ」る実装の例としてピンポンしていたけど、この事例は本来であれば互いの処理を待ち合わせる必要はまったくない。
というわけで、互いを待たずして単純に平行に走らせるとこうなる。

sub関数からselectを除去。

func sub(word string, ch chan struct{}, wg *sync.WaitGroup) {
    defer wg.Done()

    for i := 0; i < 5; i++ {
        fmt.Println(word)
        time.Sleep(100 * time.Millisecond)
    }
}

結果はNo.03と同じく5種類のワードを5回ずつ出力するけど、当然、No.03より処理時間が短い。(互いを待たないから)

$ go run main.go 
                    pung
     pong
          pang
ping
               peng
     pong
                    pung
ping
          pang
               peng
     pong
               peng
                    pung
ping
          pang
                    pung
          pang
     pong
ping
               peng
                    pung
          pang
     pong
               peng
ping

まとめ

浅いレベルの話を続けているけど、少しずつ応用編を書いていければと思う。
もっといろんなパターンをいち早く知りたい人は↓を見るといいと思う。
https://mattn.kaoriya.net/software/lang/go/20180531104907.htm
https://mattn.kaoriya.net/software/lang/go/20160706165757.htm
https://mattn.kaoriya.net/software/lang/go/20171221111857.htm

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2