go testの並列性についてのメモ

1 min読了の目安(約1200字TECH技術記事

Go言語のテスト実行コマンド go test では複数のパッケージを同時にテストできます。 go test ./... とか go test ./foo ./bar とかいった感じのテストの実行方法を見たことがあるかと思います。この時 Go は各パッケージのテストを並列で実行しているのか直列なのか気になり、ソースコードを読んでみました。

結論を先に言うとGoは各パッケージのテストを並列で実行しています。その並列度の最大はデフォルトで runtime.NumCPU() です。

以下解説。

go test は渡されたパッケージ1つ1つをビルド、テスト実行、結果印刷の3つのタスクに分けます。この時タスク間には依存関係を設定します。結果印刷はテスト実行に、テスト実行はビルドに依存します。なおコード上はタスクではなく Action という名称を使っています。つまり10個のパッケージを同時にテストしようとすると30個のタスクが生成されます。

そうやって作ったすべてのパッケージ用の結果印刷タスクに依存する形でルートタスクを生成し、そのルートタスクをタスクランナーで実行します。するとタスクランナーが依存関係を解決しつつ全タスクを実行し、全パッケージのテスト結果が出揃うという寸法です。いわゆるタスクグラフってやつですね。なおタスクランナーは "cmd/go/internal/work" パッケージの Builder 型として実装されています。

タスクランナーはタスクを実行する際に決められた個数のワーカーgoroutineを起動し、それらを用いてタスクを実行していきます。この数は build flags の -p で決まっており、これのデフォルトが runtime.NumCPU() となっています。

以上のことからパッケージ間のテストは同時に実行されうることがわかります。順番が安定しているか、タスクの解決方法の優先方向とかはちゃんと読んでないのでわかりません。しかしテスト内でパッケージを超えて共有リソース(DBとか)を触る場合は同時に実行されうることを意識しないといけないでしょう。

まぁテストで共有リソースを持たないほうが良いのは当然なんですが。

参考資料