For loop 내에서 goroutine 을 쓸 때 유의사항을 알아보자.
// send only channel parameter
// response code가 2XX 이 아니면 error 반환.
func hitUrl(url string, channel chan<- responseResult) error {
response, error := http.Get(url)
// send 전용인지 receive 전용인지 명확하게 명시.
if error != nil {
return errRequestFailed
}
// send data to channel
channel <- responseResult{url: url, statusCode: response.StatusCode}
if isSuccess(response.StatusCode) {
return nil
} else {
return errInvalidRequest
}
}
func main() {
channel := make(chan responseResult)
urls := []string{
"https://www.airbnb.com",
"https://www.google.com",
"https://www.naver.com/",
"https://www.facebook.com/",
}
// some problem occurs!
// ❌❌❌ 여기가 문제다 ❌❌❌
for index, url := range urls {
//waitGroup.Add(1)
fmt.Printf("index : %d, url: %s\n", index, url)
go func() {
// 여기로 url 이 왜 다 안넘어가지?
err := hitUrl(url, channel)
if err != nil {
log.Fatalln(err)
}
}()
fmt.Printf("22222 index : %d, url: %s\n", index, url)
}
// 여기서는 range-based for loop 보다
// 호출 횟수를 여러번하는게 좋음.
// 호출 순서 상관없이 channel Message Queue (FIFO Queue) 에 먼저 추가된 메시지부터 아무거나 받아오기 때문
for i:= 0; i < len(urls); i++ {
fmt.Printf("result: %v\n", <-channel)
}
}
for loop 안에서 문제가 발생한다.
for loop 이 다 돌고 나서야 IIFE (Immediately Invoked Function Expression) 이 호출된다.
index : 0, url: https://www.airbnb.com
22222 index : 0, url: https://www.airbnb.com
index : 1, url: https://www.google.com
22222 index : 1, url: https://www.google.com
index : 2, url: https://www.naver.com/
22222 index : 2, url: https://www.naver.com/
index : 3, url: https://www.facebook.com/
22222 index : 3, url: https://www.facebook.com/
result: {https://www.facebook.com/ 200} // 맨 마지막 Loop 의 facebook 만 출력된다.
result: {https://www.facebook.com/ 200}
result: {https://www.facebook.com/ 200}
result: {https://www.facebook.com/ 200}
range-based for loop 에서는 사실
내부적으로는 모든 대상 객체의 elements를 복사해 놓고 index, value 를 매 루프마다 임시로 할당한다.
아래 코드는
for index, value := range array {
// do something
}
내부적에서 다음과 같이 동작한다.
// 일단 임시 객체를 복사함.
len_temp := len(range)
range_temp := range
for index_temp = 0; index_temp < len_temp; index_temp++ {
value_temp = range_temp[index_temp]
index = index_temp
value = value_temp
// do something
}
👉 우리가 지정한 for loop 속
goroutine
이 실제로 실행되기 전에 loop 을 다 돈 상태에서 원소 값을 참조하면서 임시 value 값 (마지막 원소의 복제본)만 반복해서 얻어오게 되는 것이다.
아래 코드를 추가하여 for loop 을 일부러 지연시켜서 돌리면 실제로 정상 값이 출력된다.
for index, url := range urls {
fmt.Printf("index : %d, url: %s\n", index, url)
go func() {
fmt.Println("index in goroutine : ", index)
err := hitUrl(url, channel)
// 중략..
}()
// 이렇게 지체시켜서 loop 을 돌리면 정상출력된다.
// 그치만 실행 속도가 지연되므로 구데기 방법이다.
time.Sleep(time.Millisecond * 300)
fmt.Printf("22222 index : %d, url: %s\n", index, url)
}
IIFE사용시 local variable parameter 를 호출시에 넘긴다.
// goroutine!
for index, url := range urls {
fmt.Printf("index : %d, url: %s\n", index, url)
go func(url string) {
// ✅ loop 내에서 현재 사용되는 temp_value (url) 을 넘긴다.
err := hitUrl(url, channel)
if err != nil {
log.Fatalln(err)
}
}(url)
}
index : 0, url: https://www.airbnb.com
index : 1, url: https://www.google.com
index : 2, url: https://www.naver.com/
index : 3, url: https://www.facebook.com/
result: {https://www.airbnb.com 200} // 요청이 완료된 것부터 정상 출력
result: {https://www.naver.com/ 200}
result: {https://www.google.com 200}
result: {https://www.facebook.com/ 200}
Go 처음사용하면서 방금전까지 틀린 예제와 같은 유형의 코드를 작성했어요
똑같은 데이터만 전달되고있어서 해결방법을 찾다가 누군가 나와 같은 고민을 하고있겠지 싶어서 검색했는데..
덕분에 잘 해결했습니다. 정말 감사합니다 ㅎㅎ