Go 1.22에서 추가된 for loop 변수 스코프 변경
문제 상황
레거시 배치 작업을 리팩토링하던 중 Go 1.22 업그레이드 이슈를 겪었다. 기존 코드는 goroutine으로 병렬 처리를 하는데, 간헐적으로 같은 값이 중복 처리되는 버그가 있었다.
for _, item := range items {
go func() {
process(item) // 모든 goroutine이 마지막 item 처리
}()
}
기존엔 loop 변수가 반복마다 재사용되어서 클로저에서 참조하면 마지막 값만 잡혔다. 그래서 명시적으로 변수를 복사해야 했다.
for _, item := range items {
item := item // 이렇게 복사
go func() {
process(item)
}()
}
Go 1.22 변경사항
Go 1.22부터는 loop 변수가 매 반복마다 새로 생성된다. 더 이상 item := item 같은 workaround가 필요없다.
for _, item := range items {
go func() {
process(item) // 정상 동작
}()
}
마이그레이션 주의사항
기존 코드에서 의도적으로 loop 변수 주소를 사용한 경우 동작이 달라질 수 있다. 우리 코드베이스에서는 발견되지 않았지만, 테스트 커버리지가 낮은 부분은 주의가 필요하다.
go vet이 문제될 만한 패턴을 찾아주지만, 완벽하진 않았다. 배포 전 충분한 회귀 테스트가 필수다.
결론
10년 가까이 Go 개발자들을 괴롭혔던 gotcha가 드디어 해결됐다. 코드가 직관적으로 동작하게 되어서 신규 개발자 온보딩할 때 설명할 게 하나 줄었다.