LoginSignup
38
40

More than 3 years have passed since last update.

goの並列処理入門編 goroutineとchannel

Last updated at Posted at 2017-04-19

ReadMeFirst

最初、Context周りを色々調べた結果を記事にしていたのですが、
前提で並列処理がイメージつかないとアレかなぁ
と思ったので、goの並列処理について簡単にパパッとまとめておきます。

Goの並列処理はカジュアルなので、世に記事が溢れてるのはご愛嬌で。

概念 (goroutine and channel)

Goの並行処理を構成する2つの要素について簡単に。

goroutine

Goのランタイムによって管理される軽量スレッド。
関数の呼び出しに go をつけるだけで立ち上げることができる。
go Doanything(ctx) みたいな感じ。

channel

ざっくりリファレンスから抜粋。

並列実行された関数間における値の送受信を提供する型です。
宣言はchannelで扱う値の型と共に行います。
送信・受信それぞれの専用とすることも可能です。

chan T  // T型の送信・受信 を宣言
chan <- float64 //  float64の送信専用
<- chan int // intの受信専用

chan bool<- chan bool は別の型になりますので、ビルド時にエラーになる。
cannot use sig (type chan<- bool) as type chan bool

初期化はmakeで。第2引数はbufferの最大長です。

make(chan int,100) 

専用のオペレーターを使って値の送受信ができる。

  • 値の送信は channel <- 値
  • 値の受信は <-channel
  • len(channel)で現在格納されている数を計る事もできる(buffer指定しないと0のまま)

channelの値は格納した順に取り出されます。

buffer数値はdefaultは0ですが、>0の値を設定した場合、
最大長以上の書き込みをBlock(待ち)する事を利用して同時実行数を
制限するような使い方もあります。

close(channel)でchannelを閉じる事ができます。

簡単な例

Sample

package main

import (
    "fmt"
    "time"
)

func main() {
    const totalExecuteNum = 6   //合計実行数
    const maxConcurrencyNum = 3 //同時実行数
    sig := make(chan string, maxConcurrencyNum)
    res := make(chan string, totalExecuteNum)
    defer close(sig)
    defer close(res)
    fmt.Printf("start concurrency execute %s \n", time.Now())
    for i := 0; i < totalExecuteNum; i++ {
        go wait6Sec(sig, res, fmt.Sprintf("no%d", i))
    }
    for {
        //全部が終わるまで待つ
        if len(res) >= totalExecuteNum {
            break
        }
    }

    fmt.Printf("end concurrency execute %s \n", time.Now())
}

func wait6Sec(sig chan string, res chan string, name string) {
    sig <- fmt.Sprintf("sig %s", name)
    time.Sleep(6 * time.Second)
    fmt.Printf("%s:end wait 6sec \n", name)
    res <- fmt.Sprintf("sig %s", name)
    <-sig
}

実行結果

go run main5.go
start concurrency execute 2017-04-19 18:54:43.276968001 +0900 JST
no2:end wait 6sec
no3:end wait 6sec
no1:end wait 6sec
no5:end wait 6sec
no4:end wait 6sec
no0:end wait 6sec
end concurrency execute 2017-04-19 18:54:55.279826923 +0900 JST

channelのbuffer数を利用して同時実行数を制限してみました。
(合計回数(6) / 同時実行数(3)) * 6 = 12秒程度で終わってますね :)

IMO

カジュアルに使えるので、積極的に使うべきではあるけど、
基本的に並列処理自体、直列の処理より頭のリソースを使うし、
複数人開発で無秩序に色んなところで使われると、goroutineがバカスカ立ち上がったり(queueingで対策すれば良いけど)など
注意するべき所もあります:P
適用部分がサービスにとって、とにかく速度が大事な部分かどうかと言う、
複雑さと速度改善は天秤にかけるべきかな、と。

補足

contextの記事書く前に補足記事的に書いたのですが、
実際には、mattnさんが紹介してるsync package
などもあるので、見てみると良いのかな〜と思います:)

参考

Channel types
Tour of Go
Big Sky

38
40
1

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
38
40