Deprecated !!! Go 1.22 부터 이 문제는 해결되었습니다.
클로저가 loop 되는 변수를 포섭하면 원치않은 결과를 얻을 수 있다.
func test() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(i)
}()
}
wg.Wait()
}
위의 함수를 실행시키면 0, 1, 2, 3, 4 가 출력될 것 같지만 사실 그렇게 출력되지 않는다. 그 이유는 다음과 같다.
- 위 함수의 호출을 통해 생성되는 goroutine 들이 모두 같은 i 변수를 참조한다.
- 즉, 각각의 고루틴으로 생성된 클로저들이 모두 같은 변수 i를 공유한다는 것이다.
- 각각의 클로저가 실행되면서
fmt.Println(i)
라인이 호출될 때의 i는 모두가 공유하는 그 변수 i 인 것이다. - 메인 루틴이 i를 0부터 5까지 증가 시키며 전역변수 i를 출력하는 클로저 함수를 만들고 실행시킨다. (4가아니라 5까지 증가되는 이유는 ++ 연산이기 떄문)
- 각각의 고루틴은 전역변수 i를 캡쳐하는데, 이 변수는 고루틴이 생성되는 시점에서 고정되지 않고, i를 호출하는 시점에 전역변수 i를 참조한다. 이를 도식화 하면 다음과 같다.
- 위의 과정에 따라 결국 5 까지 증가된 i를 출력해 모든 루틴이 5를 출력하게 되는 것이다.
그래서 이렇게 해야함 #1
func test() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Println(i)
}(i)
}
wg.Wait()
}
- 클로저 함수가 생성되는 시점의 i를 캡쳐해서 이를 인자로 받도록 한다.
그래서 이렇게 해야함 #2
func test() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
i := i
go func() {
defer wg.Done()
fmt.Println(i)
}()
}
wg.Wait()
}
- intermediate variable 을 추가해서 클로저 함수가 생성되는 시점의 i를 잘 캡쳐할 수 있도록 한다.