[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)
  • 블로그 메뉴

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

  • 공지사항

  • 인기 글

  • 태그

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

  • 최근 글

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

티스토리툴바