go testの並列性についてのメモ
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とか)を触る場合は同時に実行されうることを意識しないといけないでしょう。
まぁテストで共有リソースを持たないほうが良いのは当然なんですが。
Discussion