Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

GAE/Goとsyncパッケージ

酔いどれGCPUGで発表した資料です。
https://gcpug-tokyo.connpass.com/event/78667/

  • Be the first to comment

GAE/Goとsyncパッケージ

  1. 1. The Go gopher was designed by Renée French. The gopher stickers was made by Takuya Ueda. Licensed under the Creative Commons 3.0 Attributions license. GAE/Goとsyncパッケージ 2018年3月2日(金) @酔いどれGCPUG
  2. 2. 自己紹介 上田拓也 @tenntenn 所属 コミュニティ活動 & Go ビギナーズ Go Conference 上田拓也 @tenntenn
  3. 3. ソウゾウ エキスパートチーム 技術をアウトプットするところに技術は集まる ■ エキスパートチームとは? ● 50%以上の時間を技術コミュニティへの貢献に充てる ■ エキスパートチームの役割 ● 社内に新しい技術を取り取り込む ● 社外のコミュニティなどを通じて社会へ還元する ■ エキスパートチームの活動 ● カンファレンス・勉強会の開催/運営 ● 対外的な講演活動 ● 執筆、雑誌への寄稿、インタビュー ● 社内外での担当技術の普及推進 @tenntenn 担当:Go・GCP @mhidaka 担当:Android メンバー
  4. 4. アジェンダ ■ ゴールーチンとチャネル ■ GAE/Goでのゴールーチンの使い所 ■ syncパッケージ ■ golang.org/x/syncパッケージ ■ github.com/tenntenn/syncパッケージ ■ まとめ
  5. 5. Goの並行処理 ■ ゴールーチン ■ 軽量なスレッドに近いもの ■ goキーワードをつけて関数呼び出し ■ チャネル ■ ゴールーチン間のデータのやり取り ■ 安全にデータをやり取りできる 5 チャネル ゴールーチン A ゴールーチン B データ データ // 関数fを別のゴールーチンで呼び出す go f()
  6. 6. GAE/Goでの並行処理の使い所 ■ 並列度は1 ● GAE/Go上ではゴールーチンは並列には実行されない ● ずっと1とは限らないので適切にロックは取ろう ■ ゴールーチンのスケジューラの挙動を把握する ● 特定の処理が走ると別のゴールーチンに処理が切り替わる ○ I/Oやシステムコール、チャネルのブロックなど ● Datastoreへ複数のリクエストを投げる場合には有効 ○ 通信が発生しているため Cloud Datastore App Engine Get A Get B Get C
  7. 7. ゴールーチン間のデータ競合 ゴールーチン-main ゴールーチン-2ゴールーチン-1 go f1() go f2() 変数v print(v) v = 100 処理順序が保証されない 競合 7
  8. 8. データ競合の解決 ■ 問題点 ■ どのゴールーチンが先にアクセスするか分からない ■ 値の変更や参照が競合する ■ 解決方法 ■ 1つの変数には1つのゴールーチンからアクセスする ■ チャネルを使ってゴールーチン間で通信をする ■ またはロックをとる(syncパッケージ) Do not communicate by sharing memory; instead, share memory by communicating 8
  9. 9. syncパッケージ ■ チャネル以外を使う理由 ● チャネルだけを使っているとコードが難解になる場合がある ● 複数のチャネルが登場したり ● 競合を防ぎたいデータが複数ある場合 ■ syncパッケージ ● データの競合を防ぐロックなどを提供するパッケージ ● sync/atomicではアトミックな演算をするための型などを提供 9
  10. 10. ロック var m sync.Mutex m.Lock() go func() { time.Sleep(3 * time.Second) m.Unlock() fmt.Println("unlock 1") }() m.Lock() m.Unlock() fmt.Println("unlock 2") Playgroundで動かす ゼロ値で使える ここでブロック 10 ■ sync.Mutex ● Lockメソッドを呼ぶとUnlockメソッドが呼ばれるまで Lockメソッドの呼び出しでブロックする
  11. 11. 書き込み・読み込みロック ■ sync.RWMutex ● Mutexに読み込み用のRLockとRUnlockが入ったもの var m sync.RWMutex m.RLock() go func() { time.Sleep(3 * time.Second) m.RUnlock() fmt.Println("unlock 1") }() m.RLock() m.RUnlock() fmt.Println("unlock 2") Playgroundで動かす 読み込みロックだけでは ブロックしない 11
  12. 12. 1度しか実行しない関数 ■ sync.Onceを使う ● 1回以上Doメソッドを呼んでも意味がない ● 複数のゴールーチンから1回しか呼ばないようにするために利用する func f() { fmt.Println("Do!!") } func main() { var once sync.Once once.Do(f) once.Do(f) fmt.Println("done") } 12 2回目は実行されない Playgroundで動かす
  13. 13. sync.Onceを使って初期化を行う ■ init関数を用いる ● context.Contextを取得できないのでログすらだせない ■ /_ah/warmupリクエストで処理する ● インスタンス起動時に送られてくるリクエスト ● app.yamlで設定して、ハンドラを設定すればよい ● しかし、必ず最初にリクエストがくるわけではない ○ sync.Onceで処理して必ず1回だけ実行されるようにする inbound_services: - warmup warmup first requst 初期化 処理 sync.Once 1回だけ実行
  14. 14. 複数のゴールーチンの待機 ■ sync.WaitGroupを使う var wg sync.WaitGroup wg.Add(1) go func() { /* do something */ wg.Done() }() wg.Add(1) go func() { /* do something */ wg.Done() }() wg.Wait() Playgroundで動かす 14
  15. 15. エラーを返すゴールーチンの待機 ■ golang.org/x/sync/errgroupを使う ● 失敗した場合にエラーが取得できる ● 1つでもエラーを起こすとキャンセルされる var eg errgroup.Group eg.Go(func() error { /*...*/ }) eg.Go(func() error { /*...*/ }) if err := eg.Wait(); err != nil { log.Fatal(err) } 15
  16. 16. golang.org/x/syncパッケージ ■ errgroupパッケージ ● エラーを返すゴールーチンの待機 ■ singleflightパッケージ ● 1度しかリクエストが飛ばないようにする ● 2度目は同じ値を返す ■ semaphoreパッケージ ● 重み付きセマフォ ■ syncmapパッケージ ● スレッドセーフなマップ ● Go1.9から標準パッケージになった
  17. 17. github.com/tenntenn/syncパッケージ ■ recoverableパッケージ ● ゴールーチンまたいだpanicを安全に処理する ■ fcfsパッケージ ● 早い物勝ち(First Come First Serve) ● 最初に処理したものだけ有効にする ■ groutinegroupパッケージ ● x/errgroupパッケージのエラーが発生しても全部続ける版 ● まだ非公開 ● メルカリ アッテで使われている
  18. 18. recoverableパッケージ ■ ゴールーチンを跨いでpanicをrecoverできない ■ panicをエラーに変換する func main() { defer func() { fmt.Println(recover()) }() go func() { panic("PANIC!!!") }() time.Sleep(1 * time.Second) } func main() { var eg errgroup.Group eg.Go(recoverable.Func(func() { panic("PANIC!!!") })) if err := eg.Wait(); err != nil { /* エラー処理 */ } }
  19. 19. fcfsパッケージ ■ 早い者勝ち(First Come First Served) ● とあるサービスからリソースを取得することを考える ● 1回目のリクエストを投げる ● 1回目が一定期間で帰ってこなかったらもう一つ投げる ● 早く帰ってきた方を使って、他方をキャンセルする リクエスト リクエスト 100ms待機
  20. 20. fcfsパッケージ ■ 早い者勝ち(First Come First Served) ● とあるサービスからリソースを取得することを考える ● 1回目のリクエストを投げる ● 1回目が一定期間で帰ってこなかったらもう一つ投げる ● 早く帰ってきた方を使って、他方をキャンセルする var g fcfs.Group f := func() (interface{}, error) { /* 処理 */ } g.Go(f) g.Delay(100 * time.Millisecond, f) // 100ms遅れて処理 v, err := g.Wait() if err != nil { /* エラー処理 */} /* vを使った処理 */
  21. 21. goroutinegroupパッケージ ■ エラーと処理結果をそれぞれ取り出せる ● エラーが発生しても他の処理には影響しない ● エラーと結果が安全に取り出せる var g goroutinegroup.Group g.Go("処理1", func() (interface{}, error) { /* 処理1 */ }) g.Go("処理2", func() (interface{}, error) { /* 処理1 */ }) <-g.Done() v1, err := g.Return("処理1") if err != nil { /* エラー処理 */} v2, err := g.Return("処理2") if err != nil { /* エラー処理 */} /* v1とv2を使った処理 */
  22. 22. まとめ ■ GAE/Goでゴールーチンは有効 ● 現在の並列度は1 ○ ずっと1とは限らないので注意 ● 複数のDatastoreへのアクセスなどを並行に投げれる ■ syncパッケージを使おう ● 初期化処理はsync.Onceで行う ● Group系の型を使えば楽にゴールーチンを利用できる
  23. 23. Thank you! twitter: @tenntenn Qiita: tenntenn connpass: tenntenn 23

×