LoginSignup
2
3

More than 5 years have passed since last update.

goroutineあれこれ

Last updated at Posted at 2018-10-24

お題

表題の通り。
goroutineを使った実装パターンを知りたければ下記を読むといい。
https://mattn.kaoriya.net/software/lang/go/20180531104907.htm
が、パターンはパターンとして、goroutineをそもそも使わないケースから始めて、使うとどうなるか、うまく動かないケースも含めて試してみようと思う。

関連記事Index

開発環境

# OS

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

# 言語

$ go version
go version go1.10 linux/amd64

実践

$共通に使う関数$

100ミリ秒のスリープを入れつつ5回「ping」を出力するping()関数と同様に「pong」を出力するpong()関数を用意

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

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

■関数の前に「go」と付けるだけでメイン関数と別の goroutine が生成される

No.01: goroutine使わない版

func main() {
    ping()
    pong()
}

当然のように5回「ping」の後、5回「pong」を出力(何回やっても結果は同じ)

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

No.02: 先行して実行する関数をgoroutine化

func main() {
    go ping()
    pong()
}

ping()関数はメイン処理をブロックせずに実行されるようになったため、即座にpong()関数の実行も始まる。

<1回目>

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

<2回目>

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

<3回目>

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

No.03: 後攻の関数をgoroutine化

func main() {
    ping()
    go pong()
}

後攻の関数がメイン関数をブロックしなくなると、それ以降にメイン関数の処理がないため、メイン関数が終了する。
メイン関数が終了すると、goroutineも終了するため、5回の「ping」を出力したら(「pong」の出力は一切なしで)終了。

$ go run main.go 
ping
ping
ping
ping
ping

No.04: 先行・後攻の関数をともにgoroutine化

func main() {
    go ping()
    go pong()
}

メイン関数をブロックする処理がなくなるのでping()関数ないしpong()関数が出力する前にメイン関数が終了する。

$ go run main.go 

■生成した goroutine の終了を待ってメイン関数を終了させる

No.05: 2つのgoroutineの終了をメイン関数で待機(Sleep編)

func main() {
    go ping()
    go pong()
    time.Sleep(3 * time.Second)
}

goroutine 生成から3秒経過したら(goroutineごと)メイン関数が終了
ただし、このやり方は悪手。

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

No.06: 2つのgoroutineの終了をメイン関数で待機(WaitGroup編)

func main() {
    wg := &sync.WaitGroup{}
    wg.Add(1)
    go ping(wg)
    wg.Add(1)
    go pong(wg)
    wg.Wait()
}

func ping(wg *sync.WaitGroup) {
    defer wg.Done()

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

func pong(wg *sync.WaitGroup) {
    defer wg.Done()

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

ちゃんとピンポン出る。

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

No.07: 2つのgoroutineの終了をメイン関数で待機(Channel編)

func main() {
    ch := make(chan struct{}, 2)
    go ping(ch)
    go pong(ch)
    <-ch
}

func ping(ch chan struct{}) {
    for i := 0; i < 5; i++ {
        fmt.Println("ping")
        time.Sleep(100 * time.Millisecond)
    }
    ch <- struct{}{}
}

func pong(ch chan struct{}) {
    for i := 0; i < 5; i++ {
        fmt.Println("     pong")
        time.Sleep(100 * time.Millisecond)
    }
    ch <- struct{}{}
}

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

まとめ

goroutineやChannelが真価を発揮する大分手前の時点だけど、今回はさわりだけ。

2
3
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
2
3