2023-05-01

Sean Kim·2023년 5월 1일
0

TIL

목록 보기
17/17

클로저가 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 인 것이다.
  1. 메인 루틴이 i를 0부터 5까지 증가 시키며 전역변수 i를 출력하는 클로저 함수를 만들고 실행시킨다. (4가아니라 5까지 증가되는 이유는 ++ 연산이기 떄문)
  2. 각각의 고루틴은 전역변수 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를 잘 캡쳐할 수 있도록 한다.
profile
이것저것 해보고있습니다.

0개의 댓글