概要
goroutineはGo言語において花形とも言える機能ですが、
実際に動くものを作ろうとなると落とし穴も多く非常に難しいです。
これは防備録を兼ねたチートシートです。
そのままコピペしてgo run
すれば動くと思います。
随時書き足していきます
このドキュメントで使用しているコードはgitHubにまとめてあります
https://github.com/nc30/golang_examples/tree/main/goroutine
基本形
basic.go
package main
import (
"log"
"time"
)
// goroutineはpythonで言うthureadingのようにfunction単位で並列処理を行う
// go言語の花形とも言えるもので、多言語よりも簡単で安全に行うことができる
// 今回は一次関数として渡しているが、もちろん関数を渡すことでも動かすことができる
func main() {
end := make(chan interface{}, 0)
go func() {
log.Println("waiting 3 seconds...")
time.Sleep(time.Second * 3)
log.Println("done!")
end <- nil
}() // 最後の()が重要
// goroutineが途中だろうがメイン関数が終了すれば容赦なく途中で終了してしまう
// そのためwait処理は必ず必要になる。
// 一番簡単なのがこのchannelを使う方法。
// 試しに<-endをコメントアウトして動かしてみよう
// きっと"waiting ~"すら表示されずに終わるはず
<-end
/*
2021/11/15 19:21:29 waiting 3 seconds...
2021/11/15 19:21:32 done!
*/
}
waitgroupを用いたブロック処理
/*
- https://pkg.go.dev/sync#WaitGroup
*/
package main
import (
"log"
"time"
"sync"
)
// httpリクエストの並列処理など、
// 同じ処理を複数並列にしたい場合はsync.WaitGroupが便利
// このままではエラー処理までハンドリングしづらいのが難点
func main() {
var wg sync.WaitGroup
list := []string {
"https://httpbin.org/get",
"https://yahoo.co.jp/",
"https://google.com/",
}
for _, url := range list{
// 開始前にAdd(1)する
wg.Add(1)
// 名前空間の関係でrangeでとったurlを直接渡すとurlの値が意図したものと変わってしまう
// その場合このように引数としてurlを渡さないといけない
// 試しにgo fund()の引数の変数名を変えてみよう
go func(url string) {
// goroutine終了時にDone()するようにする
defer wg.Done()
// http処理は今回は無関係なのでリクエストを投げてるフリ
log.Printf("requesting to %s", url)
time.Sleep(time.Second * 3)
log.Println("done!")
}(url)
}
// Add()した数字の合計 - Done()した回数が0になるまで待ち続ける。
// 試しにforの手前辺りで一回wg.Add(1)してみよう。
// 存在しない最後のDone()を待ち続けて永遠に待ち続けるはず。(Ctrl+Cで強制終了)
wg.Wait()
/*
2021/11/15 19:21:29 waiting 3 seconds...
2021/11/15 19:21:32 done!
*/
}
Contextを用いたブロック・タイムアウト処理
withContext.go
https://pkg.go.dev/context#WithCancel
https://pkg.go.dev/context#WithTimeout
/*
- https://pkg.go.dev/context#WithCancel
- https://pkg.go.dev/context#WithTimeout
*/
package main
import (
"context"
"log"
"time"
)
func basic() {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
go func() {
// 関数終了時にcontextへ終了通知を送る
defer cancel()
log.Println("waiting 3 seconds...")
time.Sleep(time.Second * 3)
log.Println("done!")
}()
<-ctx.Done()
/*
2021/11/15 19:21:29 waiting 3 seconds...
2021/11/15 19:21:32 done!
*/
}
// 通信系の処理などタイムアウトが必要な場合はcontextのTimeout機能を使うといい
// WithTimeoutやSleepの時間を変えて挙動を調べてみよう
func withTimeout() {
ctx := context.Background()
// withCancelの変わりにWithTimeoutを使用する。
ctx, cancel := context.WithTimeout(ctx, time.Second*5)
defer cancel()
go func() {
// 関数終了時にcontextへ終了通知
defer cancel()
log.Println("waiting 3 seconds...")
time.Sleep(time.Second * 3)
log.Println("done!")
}()
<-ctx.Done()
// contextがDoneになった理由はctx.Err()でわかる
log.Println(ctx.Err())
// 処理成功の可否はこれを比較することで行える
log.Println(ctx.Err() == context.Canceled)
/*
2021/11/15 19:32:17 waiting 3 seconds...
2021/11/15 19:32:20 done!
2021/11/15 19:32:20 context canceled
2021/11/15 19:32:20 true
*/
}
func main() {
basic()
withTimeout()
}
semaphoreを用いた同時処理数制限
/*
docs:
- https://pkg.go.dev/golang.org/x/sync/semaphore
*/
package main
import (
"context"
"log"
"time"
"golang.org/x/sync/semaphore"
)
// これまでの例では固定数の処理を並列処理していたが、
// これが不定数であった場合、その数がそのまま並列で動いてしまう。
// 例えば30の処理を並列でこなしたい場合、
// 一度に30を処理すると逆にパフォーマンスが落ちる可能性もある
//
// そこでWaitGroupの変わりにsemaphoreを使うことで同時に走る処理をコントロールする
func main() {
var MaxWorkers int64 = 2
// 同時処理数制御機能付きのWaitGroup的なもの
// この引数(int64)で同時に動かせる数を定義する
sem := semaphore.NewWeighted(MaxWorkers)
ctx := context.Background()
for i := 0; i < 30; i++ {
// waitGroup.Add()に相当
// semaphoreに処理の開始を宣言する
// 第2引数の1は宣言する処理数。基本1固定でいい
// 処理上限内で空きができるまでブロックされる
if err := sem.Acquire(ctx, 1); err != nil {
log.Printf("Failed to acquire semaphore: %v", err)
break
}
go func(number int) {
// waitGroup.Done()に相当
// これが重要。
// ここで処理が終わったことを通知すると、
// 空きができたと判断され上のブロックが解除される。
//
// 整合性にズレが出ないよう、goroutine内のできるだけ早い段階で
// 必ずdeferを使ってリリースする
defer sem.Release(1)
// 適当に待つ
log.Println("starting number:", number)
time.Sleep(time.Millisecond * 500)
log.Println("end of number:", number)
}(i)
}
// waitGroup.Wait()に相当
// 同時処理上限分の空きが確保できる = 処理が全部終了したと判断できる
sem.Acquire(ctx, MaxWorkers)
log.Println("all done")
/*
2021/12/27 14:38:41 starting number: 1
2021/12/27 14:38:41 starting number: 0
2021/12/27 14:38:42 end of number: 0
2021/12/27 14:38:42 end of number: 1
2021/12/27 14:38:42 starting number: 2
2021/12/27 14:38:42 starting number: 3
2021/12/27 14:38:42 end of number: 2
2021/12/27 14:38:42 starting number: 4
2021/12/27 14:38:42 end of number: 3
2021/12/27 14:38:42 starting number: 5
2021/12/27 14:38:43 end of number: 4
2021/12/27 14:38:43 starting number: 6
2021/12/27 14:38:43 end of number: 5
2021/12/27 14:38:43 starting number: 7
2021/12/27 14:38:43 end of number: 6
2021/12/27 14:38:43 end of number: 7
2021/12/27 14:38:43 starting number: 8
2021/12/27 14:38:43 starting number: 9
2021/12/27 14:38:44 end of number: 8
2021/12/27 14:38:44 starting number: 10
2021/12/27 14:38:44 end of number: 9
2021/12/27 14:38:44 starting number: 11
2021/12/27 14:38:44 end of number: 10
2021/12/27 14:38:44 starting number: 12
2021/12/27 14:38:44 end of number: 11
2021/12/27 14:38:44 starting number: 13
2021/12/27 14:38:45 end of number: 12
2021/12/27 14:38:45 starting number: 14
2021/12/27 14:38:45 end of number: 13
2021/12/27 14:38:45 starting number: 15
2021/12/27 14:38:45 end of number: 14
2021/12/27 14:38:45 starting number: 16
2021/12/27 14:38:45 end of number: 15
2021/12/27 14:38:45 starting number: 17
2021/12/27 14:38:46 end of number: 16
2021/12/27 14:38:46 starting number: 18
2021/12/27 14:38:46 end of number: 17
2021/12/27 14:38:46 starting number: 19
2021/12/27 14:38:46 end of number: 18
2021/12/27 14:38:46 starting number: 20
2021/12/27 14:38:46 end of number: 19
2021/12/27 14:38:46 starting number: 21
2021/12/27 14:38:47 end of number: 20
2021/12/27 14:38:47 starting number: 22
2021/12/27 14:38:47 end of number: 21
2021/12/27 14:38:47 starting number: 23
2021/12/27 14:38:47 end of number: 22
2021/12/27 14:38:47 starting number: 24
2021/12/27 14:38:47 end of number: 23
2021/12/27 14:38:47 starting number: 25
2021/12/27 14:38:48 end of number: 25
2021/12/27 14:38:48 end of number: 24
2021/12/27 14:38:48 starting number: 26
2021/12/27 14:38:48 starting number: 27
2021/12/27 14:38:48 end of number: 27
2021/12/27 14:38:48 starting number: 28
2021/12/27 14:38:48 end of number: 26
2021/12/27 14:38:48 starting number: 29
2021/12/27 14:38:49 end of number: 28
2021/12/27 14:38:49 end of number: 29
2021/12/27 14:38:49 all done
*/
}