클로저를 고루틴으로 실행할때 주의할점.

go routine 만들 떄 익명함수로 할때 마지막 변수갑스로 다 써서 개망할뻔

Closure(클로저)는 함수 바깥에 있는 변수를 참조하는 함수값을 일컫는다. 함수가 선언될 때의 환경을 계속 유지하여 프로그램의 흐름을 변수에 저장하기 위해 사용한다. 클로저는 함수형 언어의 큰 특징이며, Go 는 클로저를 통해 함수형 언어의 기능을 구현하고 있다.

참고: http://pyrasis.com/book/GoForTheReallyImpatient/Unit25

클로저를 고루틴으로 실행하기

반복문 내에 go routine를 삽입하여 concurrent한 Request를 날리는 로직을 작성하고 싶다고 하자.

처음엔 이런식으로 하면 될줄 알았다.

package main
 
import (
    "fmt"
)
 
func main() {
    user := "helloworld"
 
    for i := 0; i < 100; i++ {
        go func() {
            http.Get("http://post.naver.com/%v/%v", user, i) // 반복문의 변수를 클로저에서 바로 사용
        }()
    }
}

예상한 결과

http.Get("http://post.naver.com/helloworld/1")
http.Get("http://post.naver.com/helloworld/2")
http.Get("http://post.naver.com/helloworld/3")
...
http.Get("http://post.naver.com/helloworld/99")

실제 결과

http.Get("http://post.naver.com/helloworld/100")
http.Get("http://post.naver.com/helloworld/100")
http.Get("http://post.naver.com/helloworld/100")
...
http.Get("http://post.naver.com/helloworld/100")

그렇게 되는 이유는,

클로저를 고루틴으로 사용할때는 반복문이 끝난 뒤에 고루틴이 실행되기 때문이다. 따라서, 고루틴이 생성된 시점의 변수 i의 값은 100이다. 따라서 모두

http.Get("http://post.naver.com/helloworld/100")

값으로 요청이 날라가게 되는것이다. 따라서 클로저를 고루틴으로 실행할때 반복문에 의해 바뀌는 변수는 반드시 파라미터로 넘겨줘야한다. 파라미터로 넘겨주는 시점에 해당 변수의 값이 복사되므로, 고루틴이 생성될때 그대로 사용할 수 있다.

package main
 
import (
    "fmt"
)
 
func main() {
    user := "helloworld"
 
    for i := 0; i < 100; i++ {
        go func(n int) {          // 익명 함수를 고루틴으로 실행(클로저)
            http.Get("http://post.naver.com/%v/%v", user, n)
        }(i)                      // 반복문의 변수는 매개변수로 넘겨줌
    }
}