はじめに
こちらの記事で
GoでLTやることになったので準備用のメモ
GoのLTのメモを書きましたが、
Goの良いところの1つである
goroutine(ゴルーチン)について書けなかったので追加記事を書いておきます。
通常バージョンから
読み書き(push・pull)並行バージョン。
更に、pushも並行でpullも並行バージョン。
などなど
色んなサンプルを書いてみます。
goroutineとは
簡単に並行処理をさせることができるGoの機能。
並行処理ができるので当然早い。
何よりもサンプル
まずは普通のfor文。
func main() {
number := []int{1, 2, 3, 4, 5}
serial(number)
}
func serial(number []int) {
for _, n := range number {
fmt.Println("serial: ", n)
}
}
serial: 1
serial: 2
serial: 3
serial: 4
serial: 5
goroutineで並行処理
並行処理させたいところで go func
をするだけ。
これから並行処理ができてしまう。
func main() {
number := []int{1, 2, 3, 4, 5}
parallel(number)
}
func parallel(number []int) {
for _, n := range number {
go func(n int) {
fmt.Println("parallel: ", n)
}(n)
}
}
結果なし
なぜ結果なし!?
それは簡単で
並行処理になるので Print
される前にプロセスが終わってしまうから。
とりあえず待たせてみる
最後に 1秒sleep
を追加。
func parallel(number []int) {
for _, n := range number {
go func(n int) {
fmt.Println("parallel: ", n)
}(n)
}
time.Sleep(time.Second)
}
parallel: 2
parallel: 3
parallel: 1
parallel: 5
parallel: 4
並行処理になるので、1~5がバラバラに表示される。
何秒待てば良いの?
sleep
するだけでは待ち時間が分からないと思うので
きちんとした待ち制御を入れるには sync
を使う。
func parallel_wg(number []int) {
var wg sync.WaitGroup
for _, n := range number {
wg.Add(1)
go func(n int) {
fmt.Println("parallel_wg: ", n)
wg.Done()
}(n)
}
wg.Wait()
}
parallel_wg: 5
parallel_wg: 1
parallel_wg: 2
parallel_wg: 3
parallel_wg: 4
これでOK。
readとwriteを並行にすることもできる
その場合には channel
を使う。
pushされたものからpullできる。
func parallel_channel(number []int) {
res := make(chan int, len(number))
go func() {
defer close(res)
for _, n := range number {
fmt.Println("parallel_channel push: ", n)
res <- n
time.Sleep(time.Millisecond)
}
}()
for r := range res {
fmt.Println("parallel_channel pull: ", r)
}
}
parallel_channel push: 1
parallel_channel pull: 1
parallel_channel push: 2
parallel_channel pull: 2
parallel_channel push: 3
parallel_channel pull: 3
parallel_channel push: 4
parallel_channel pull: 4
parallel_channel push: 5
parallel_channel pull: 5
応用編
pushも並行。pullも並行。
func parallel_channel_wg(number []int) {
var wg sync.WaitGroup
res := make(chan int, len(number))
go func() {
defer close(res)
for _, n := range number {
wg.Add(1)
go func(n int) {
fmt.Println("parallel_channel_wg push: ", n)
res <- n
wg.Done()
}(n)
}
wg.Wait()
}()
for r := range res {
fmt.Println("parallel_channel_wg pull: ", r)
}
}
parallel_channel_wg push: 4
parallel_channel_wg push: 2
parallel_channel_wg push: 1
parallel_channel_wg pull: 4
parallel_channel_wg pull: 2
parallel_channel_wg pull: 1
parallel_channel_wg push: 3
parallel_channel_wg pull: 3
parallel_channel_wg push: 5
parallel_channel_wg pull: 5
色々できます
javascriptでいうところの promise.all
のようなことも簡単にできるということです。
goroutineはCPUやコア数に応じて使う数も指定できたり
やりたいことに合わせて設定できます。
ただし、勝手にスレッドセーフになる訳ではないので
goroutine内で使うデータについては注意が必要です。
⇒【2017/10/17 追記】
Goのバージョン1.9からは sync.Map
というものがあるので排他制御もお任せで大丈夫なようです!
ということで
便利なgoroutineのお話でした。
前回の記事 もそうですけど
やっぱGoって何でも簡単にできちゃうので楽で良いですね。