[Go] 클로저(Closure)와 루프 변수 캡처 버그 (Feat. Go 1.22)

2025. 11. 11. 20:28·Language/Go

회사에서 에이전트로 개발을 하는데(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)
}

 

추가로 읽어보면 좋을 자료

  • https://medium.com/@krisguttenbergovitz/go-1-22s-loop-variable-fix-solving-a-decade-old-gotcha-and-modern-concurrency-pitfalls-69aa4eb0b8a1

'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
'Language/Go' 카테고리의 다른 글
  • [Go] Prometheus 대시보드를 활용한 메트릭 분석 가이드
  • [Go] Prometheus 라이브러리로 Go 애플리케이션 프로파일링 하기
  • [Go] 배치 서버 OOM 분석을 위한 메트릭 설계와 프로파일링 전략
  • [Go] Context에 관한 고찰 - 3 : Graceful Shutdown의 미학
빵빵0
빵빵0
(아직은) 공부하고 정리하는 블로그입니다.
  • 빵빵0
    Hack Your World
    빵빵0
  • 전체
    오늘
    어제
    • 분류 전체보기 (97)
      • Error Handling (7)
      • Project (5)
        • MEV (2)
      • Architecture (0)
        • API (0)
        • Cache (0)
        • 사소한 고민거리 (0)
      • Computer Science (4)
        • Data Structure (2)
        • Database (1)
        • Cloud (0)
        • OS (0)
        • Infra, Network (1)
        • AI (0)
      • Language (12)
        • Go (11)
        • Rust (0)
        • Move (1)
        • Python (0)
        • Java (0)
      • Algorithm (40)
        • BaekJoon (18)
        • Programmers (7)
        • LeetCode (6)
        • NeetCode (9)
      • SW Books (9)
        • gRPC Up & Running (1)
        • System Design Interview (2)
        • 스프링 입문을 위한 자바 객체지향의 원리와 이해 (6)
        • 블록체인 해설서 (0)
        • 후니의 쉽게 쓴 CISCO 네트워킹 (0)
      • BlockChain (5)
        • Research (1)
        • Meetup (4)
        • Tech (0)
      • Own (8)
        • TIR(Today I Read) (3)
        • Personal (2)
        • Novel (0)
        • Memo (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Hash Table
    golang
    chart
    EVM
    two pointer
    MongoDB
    blockchain
    BEAKJOON
    2024
    블록체인
    MEV
    Palindrome
    NeetCode
    candlechart
    Greedy
    스택
    Python
    프로그래머스
    DP
    go
    context
    KBW
    ethereum
    Programmers
    백준
    goroutine
    BFS
    LeetCode
    BaekJoon
    큐
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
빵빵0
[Go] 클로저(Closure)와 루프 변수 캡처 버그 (Feat. Go 1.22)
상단으로

티스토리툴바