회사에서 에이전트로 개발을 하는데(Obviously.. 요즘 에이전트 없이 개발하는 사람도 있어?) 고루틴을 쓸 때 변수를 캡쳐해서 쓰도록 코드를 짜더라.
그 이유가 궁금해서 에이전트랑 대화하다가 배운 내용을 정리하면 좋을 것 같아서 짧게 정리해봤다
클로저(Closure)란?
- 클로저는 자신이 정의된 렉시컬 스코프(lexical scope, 다른 말로 정적 스코프(static scope))에 있는 식별자들을 잡아두는 함수
- 클로저는 '값' 아니라 '변수' 자체를 참조. 즉, 같은 변수를 여러 번 갱신하면, 그 변수를 캡처한 모든 클로저는 갱신된 값을 보게 됨
package main
import "fmt"
func main() {
x := 0
fn := func() { fmt.Println(x) }
x = 42
fn() // 42 출력: 클로저는 x '값' 복사본이 아니라 x '변수'를 본다
}
클로저와 For Loop & 고루틴
- 루프 변수는 반복마다 새 변수가 생기는 게 아니라, 같은 변수에 재대입
- 클로저는 이 '같은 변수'를 캡처하므로, 고루틴이 늦게 실행되면 최종 값만 반복해서 보게 됨.
package main
import (
"fmt"
"time"
)
func main() {
words := []string{"고", "루틴", "캡처"}
for _, w := range words {
go func() {
// Go 1.21 이하: 종종 마지막 값만 반복 출력
// Go 1.22 이상: 각 반복의 w가 올바르게 캡처됨
fmt.Println(w)
}()
}
time.Sleep(300 * time.Millisecond)
}
package main
import "fmt"
func main() {
nums := []int{1, 2, 3}
var ptrs []*int
for _, v := range nums {
ptrs = append(ptrs, &v) // 1.21 이하는 모두 같은 주소가 쌓임; 1.22 이상은 각기 다른 주소
}
for _, p := range ptrs {
fmt.Println(*p) // 1.21 이하는 같은 값 반복, 1.22부터는 1,2,3 기대대로
}
}
Go 1.22 에서의 변화
- Go 1.22 에서 for-range 반복 변수가 '각 반복마다 새로운 변수'로 동작
- 즉, 루프 스코핑 변화는 Go 언어의 타입 안정성과 코드 안정성을 크게 개선
- 따라서 위의 전형적인 for-range + 클로저 문제는 1.22 이상에서는 의도대로 동작 (각 고루틴이 각기 다른 값을 출력)
잔여 문제
- for-range의 반복 변수 자체가 새로 바인딩되는 경우는 안전해짐
- 그래도 바깥에서 미리 선언한 변수에 대입하는 형태는 위험
- 포인트/주소 패턴은 무엇의 주소를 취하는지 명확히 해야함. 그냥 명시해서 쓰는 습관을 들이는게 나음.
- 가능하면 포인터를 넘기지 말고 '값'을 인자로 넘기기
for i := range nums {
go func(p *int) {
fmt.Println(*p)
}(&nums[i]) // 의도 명확
}
- 그러나 3절 for문은 여전히 복사/인자 전달이 필요
- 매 반복마다 local 복사본 만들기 or 고루틴 인자 전달로 값 고정하기
3절 for문(three-clause for)
- for 초기화; 조건; 후위 { ... } 형태의 전통적인 C-스타일 루프
ex) for i := 0; i < n; i++ { ... }
// 문제
for i := 0; i < 3; i++ {
go func() {
fmt.Println(i) // 여전히 최종 값(또는 뒤섞인 값) 가능
}()
}
// 해결
for i := 0; i < 3; i++ {
i := i // 변수 복사
go func(i int) {
fmt.Println(i)
}(i)
}
추가로 읽어보면 좋을 자료
'Language > Go' 카테고리의 다른 글
| [Go] Prometheus 대시보드를 활용한 메트릭 분석 가이드 (0) | 2025.09.16 |
|---|---|
| [Go] Prometheus 라이브러리로 Go 애플리케이션 프로파일링 하기 (0) | 2025.08.28 |
| [Go] 배치 서버 OOM 분석을 위한 메트릭 설계와 프로파일링 전략 (0) | 2025.08.26 |
| [Go] Context에 관한 고찰 - 3 : Graceful Shutdown의 미학 (0) | 2025.07.28 |
| [Go] Context에 관한 고찰 - 2 : Context의 중요성과 메모리 누수 (0) | 2025.07.24 |