Upgrade to Pro — share decks privately, control downloads, hide ads and more …

synctestをEbitengineで可視化した話.pdf

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest. →
Avatar for nakadate00 nakadate00
March 18, 2026
91

 synctestをEbitengineで可視化した話.pdf

Avatar for nakadate00

nakadate00

March 18, 2026

Transcript

  1. https://go.dev/blog/synctest synctestとは • Go1.24で実験的に導入され、Go1.25で正式リリース • Go言語の並行処理テスト を支援するパッケージ • 2つの関数で構成 ◦

    bubble 内でテストを実行する Test 関数(旧 Run 関数) ◦ bubble 内の全てのゴルーチンがブロックされるまで待機する Wait 関数 • すべてのゴルーチンが durably block になったら進む fake clock による時間制御(初期時刻: UTC 2000-01-01 00:00:00)
  2. 10 • Go の並行処理を Ebitengine で可視化している発表を見た • synctest だけでなく Ebitengine

    もずっと触ってみたかった Ebitengine で可視化すればいいじゃない! https://ebitengine.org/ja/
  3. 12 キャッシュ処理の非同期テスト ベースとなるコードは以下を参考にし た簡単なキャッシュ処理 Go1.25新機能 testing/synctest で高速& 確実な並行テストを実現する方法 1. メインゴルーチンが爆弾を宝箱に入れ

    る (キャッシュにデータをセット) 2. サブゴルーチンが爆弾を解除 (キャッシュをクリア) 3. メインゴルーチンが宝箱を確認 (キャッシュのデータを確認) = キャッシュ = データ
  4. 16 1秒余裕を持って待機 1. メインゴルーチンが爆弾を宝箱に入れ る 2. サブゴルーチンが爆弾を解除 3. 余裕を持って 1秒待機してから

    メインゴルーチンが宝箱を確認 非同期処理のテストを100%成功させるた めの従来の手法
  5. 21 durably block に着目してみる • Go1.24で実験的に導入され、Go1.25で正式リリース • Go言語の並行処理テスト を支援するパッケージ •

    2つの関数で構成 ◦ bubble 内でテストを実行する Test 関数(旧 Run 関数) ◦ bubble 内の全てのゴルーチンがブロックされるまで待機する Wait 関数 • すべてのゴルーチンが durably block になったら進む fake clock による時間制御(初期時刻: UTC 2000-01-01 00:00:00)
  6. https://go.dev/blog/synctest durably blockとは The complete list of operations which durably

    block a goroutine is: • a send or receive on a nil channel • a send or receive blocked on a channel created within the same bubble • a select statement where every case is durably blocking • time.Sleep • sync.Cond.Wait • sync.WaitGroup.Wait
  7. https://go.dev/blog/synctest durably blockとは ゴルーチンを永続的にブロックする操作の完全なリストは以下: • nil チャネルでの送信または受信 • 同一 bubble

    内で作成されたチャネルで、ブロックされた送信または受信 • すべてのケースが永続的にブロックする select 文 • time.Sleep • sync.Cond.Wait • sync.WaitGroup.Wait
  8. https://go.dev/blog/synctest durably blockとは ゴルーチンを永続的にブロックする操作の完全なリストは以下: • nil チャネルでの送信または受信 • 同一 bubble

    内で作成されたチャネルで、ブロックされた送信または受信 • すべてのケースが永続的にブロックする select 文 • time.Sleep • sync.Cond.Wait • sync.WaitGroup.Wait 今回使用している操作は time.Sleep のみ
  9. 27 メインかサブのどちらかが先に time.Sleep (durably block) durably block Set time.Sleep (start)

    time.Sleep (end)i Get time.Sleep (start) time.Sleep (end) c.value = "" 1秒待機 メインゴルーチン サブゴルーチン サブが durably block ではない 時刻は進まない
  10. 28 メインかサブのどちらかが先に time.Sleep (durably block) Set time.Sleep (start) time.Sleep (end)i

    Get time.Sleep (start) time.Sleep (end) c.value = "" 1秒待機 メインゴルーチン サブゴルーチン durably block メインが durably block ではない 時刻は進まない
  11. 29 すべてのゴルーチンが durably block になったので時刻が進む Set time.Sleep (start) time.Sleep (end)i

    Get time.Sleep (start) time.Sleep (end) c.value = "" 1秒待機 メインゴルーチン サブゴルーチン durably block durably block
  12. 30 サブゴルーチンが待機を完了して動き出す Set time.Sleep (start) time.Sleep (end)i Get time.Sleep (start)

    time.Sleep (end) c.value = "" 1秒待機 メインゴルーチン サブゴルーチン
  13. 31 メインゴルーチンが 1秒待機中に爆弾解除 Set time.Sleep (start) time.Sleep (end)i Get time.Sleep

    (start) time.Sleep (end) c.value = "" 1秒待機 メインゴルーチン サブゴルーチン
  14. 32 メインゴルーチンが 1秒の待機を完了して動き出す Set time.Sleep (start) time.Sleep (end)i Get time.Sleep

    (start) time.Sleep (end) c.value = "" 1秒待機 メインゴルーチン サブゴルーチン
  15. 36 メインかサブのどちらかが先に time.Sleep (durably block) メインゴルーチン サブゴルーチン Set time.Sleep (start)

    time.Sleep (end)i Get time.Sleep (start) time.Sleep (end) c.value = "" durably block サブが durably block ではない 時刻は進まない
  16. 37 メインかサブのどちらかが先に time.Sleep (durably block) メインゴルーチン サブゴルーチン Set time.Sleep (start)

    time.Sleep (end)i Get time.Sleep (start) time.Sleep (end) c.value = "" durably block メインが durably block ではない 時刻は進まない
  17. 38 すべてのゴルーチンが durably block になったので時刻が進む メインゴルーチン サブゴルーチン Set time.Sleep (start)

    time.Sleep (end)i Get time.Sleep (start) time.Sleep (end) c.value = "" durably block durably block
  18. 42 そこで Wait 関数が役に立つ • Go1.24で実験的に導入され、Go1.25で正式リリース • Go言語の並行処理テスト を支援するパッケージ •

    2つの関数で構成 ◦ bubble 内でテストを実行する Test 関数(旧 Run 関数) ◦ bubble 内の全てのゴルーチンがブロックされるまで待機する Wait 関数 • すべてのゴルーチンが durably block になったら進む fake clock による時間制御(初期時刻: UTC 2000-01-01 00:00:00)
  19. 43 synctest.Wait を利用 1. メインゴルーチンが爆弾を宝箱に入れ る 2. サブゴルーチンが爆弾を解除 3. 爆弾解除の実施完了を確認する

    synctest.Wait 4. メインゴルーチンが宝箱を確認 Wait 関数を利用 →bubble 内の全てのゴルーチンが  ブロックされるまで待機 →爆弾解除の実施中はブロック判定  ではないので待機 →爆弾解除の実施完了を確認してから  次の処理に進む
  20. 46 よかったこと 以下の流れを      理解することができた すべてのゴルーチンが durably block になったら bubble 内の時刻が進む →

    つまり、同時にキャッシュ取得と削除の処理が実行 される → そのため、キャッシュ削除が少しでも遅れると取得時にエラーになる → キャッシュ削除ゴルーチンの処理完了を待つ synctest.Wait で解決
  21. 47 よかったこと 以下の流れを可視化しつつ 理解することができた すべてのゴルーチンが durably block になったら bubble 内の時刻が進む

    → つまり、同時にキャッシュ取得と削除の処理が実行 される 宝箱を再確認するメインと、宝箱から爆弾を取り除くサブが同時に動く → そのため、キャッシュ削除が少しでも遅れると取得時にエラーになる 宝箱から爆弾を取り除く前にメインゴルーチンが再確認してしまい爆発 → キャッシュ削除ゴルーチンの処理完了を待つ synctest.Wait で解決 メインゴルーチンが爆弾解除処理を待つ ことで安全に宝箱を再確認できる
  22. 48 • 本当は動的に synctest の処理を可視化したかった …。 ◦ testing/synctest がテストファイル専用のものだったので断念 •

    synctest.Wait での待ち時間を明確に可視化したかった …。 ◦ キャッシュ再取得のメインゴルーチンを余剰に待たせると synctest.Wait で待たなくても絶対に成功する(従来の手法) ◦ キャッシュ削除のサブゴルーチンを余剰に待たせるとその間 durably block 状態になるため、 synctest.Wait が即座にスルー → サブゴルーチンの処理が重い場合は待ち時間を可視化できそう うまくいかなかったこと(学び)
  23. 51 参考資料 • Testing concurrent code with testing/synctest • testing/synctest

    • Ebitengine • Go1.25新機能 testing/synctest で高速&確実な並行テストを実現する方法 • wuhan005/❤Gopher.GIF • かわいいフリー素材集 いらすとや