sync: negative WaitGroup counter パニックの原因と直し方
sync.WaitGroupのDone()はAdd()で加算した回数より多く呼ぶとカウンタが負になり、実行時にパニックします。goroutineの数とAddで加算する数が一致していないと発生します。
エラーメッセージの読み方
panic: sync: negative WaitGroup counter
panic- パニック — 実行時に回復不能な状態になったことを示します。deferとrecoverで捕まえない限り、そのままプロセスが終了します
sync: negative WaitGroup counter- 詳細メッセージ — どの値が問題だったか
このエラーが出る典型パターン
パターン1
1 package main 2 3 import "sync" 4 5 func main() { 6 var wg sync.WaitGroup 7 wg.Add(1) 8 wg.Done() 9 wg.Done() ^ 10 }
panic: sync: negative WaitGroup counterAdd(1)に対しDoneを2回呼んでいる
Add(1)に対してDoneを2回呼ぶと、カウンタが-1になってしまい実行時にパニックします。
直し方: wg.Done() を (空) にします。
広告
広告スロット(未設定)
パターン2
1 package main 2 3 import "sync" 4 5 func main() { 6 var wg sync.WaitGroup 7 wg.Add(2) 8 wg.Done() 9 wg.Done() 10 wg.Done() ^ 11 }
panic: sync: negative WaitGroup counterAdd(2)に対しDoneを3回呼んでいる
Add(2)で2つ分のgoroutineを待つつもりが、Doneを3回呼ぶとカウンタが負になります。
直し方: wg.Done() を (空) にします。
パターン3
1 package main 2 3 import "sync" 4 5 func main() { 6 var wg sync.WaitGroup 7 wg.Add(3) 8 wg.Done() 9 wg.Done() 10 wg.Done() 11 wg.Done() ^ 12 }
panic: sync: negative WaitGroup counterAdd(3)に対しDoneを4回呼んでいる
AddとDoneの呼び出し回数の対応が崩れると、WaitGroupは静かに壊れるのではなく即座にパニックで教えてくれます。
直し方: wg.Done() を (空) にします。
パターン4
1 package main 2 3 import "sync" 4 5 func main() { 6 var wg sync.WaitGroup 7 wg.Add(1) 8 wg.Done() 9 wg.Done() ^ 10 }
panic: sync: negative WaitGroup counterAdd(1)に対しDoneを2回呼んでいる
goroutineの数とAddで加算する数が一致していないと、このパニックの原因になります。
直し方: wg.Done() を (空) にします。
パターン5
1 package main 2 3 import "sync" 4 5 func main() { 6 var wg sync.WaitGroup 7 wg.Add(2) 8 wg.Done() 9 wg.Done() 10 wg.Done() ^ 11 }
panic: sync: negative WaitGroup counterAdd(2)に対しDoneを3回呼んでいる
defer wg.Done()を複数箇所に書いてしまうコピー&ペーストのミスも、この種のパニックの典型的な原因です。
直し方: wg.Done() を (空) にします。
よくある誤解
「Doneは呼びすぎても、カウンタが0で止まってくれるはず」という思い込みは誤りです。WaitGroupは呼び出し回数の対応を厳密にチェックしており、Addの回数を超えるDoneはその場でパニックを起こします。
まとめ
sync: negative WaitGroup counter パニックの原因と直し方は中級でつまずきやすい項目です。上の5パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。