A Tour of Go Exercise - Web Crawler 풀이
튜토리얼 자체에서는 WaitGroup에 대한 설명은 없는데, WaitGroup을 써서 풀었다.
출제자는 아마 채널을 이용해서 풀기를 의도했던 게 아닐까 싶다.
코드는 여기서 실행해볼 수 있다.
https://play.golang.org/p/Rm6J-yLHR5j
for _, u := range urls {
wg.Add(1)
go func(u string){
defer wg.Done()
Crawl(u, depth-1, fetcher, cp)
}(u)
}
Crawl 함수의 이 for문에 대해서 메모를 좀 남겨놓고 싶다.
1. WaitGroup를 안 쓰면 어떻게 되는가?
for _, u := range urls {
go Crawl(u, depth-1, fetcher, cp)
}
코드의 다른 부분들은 다 동일하게 두고 해당 for문만 위와 같이 바꾸면 어떻게 될까?
found: https://golang.org/ "The Go Programming Language" 한 줄만 출력하고 프로그램이 끝나버린다(Program exited).
매 url마다 goroutine이 생성되면서, 각 goroutine들은 자기 갈 길을 가버렸기(?) 때문이다.
main에 해당하는 스레드는 어쨌든 자기는 Crawl 함수를 실행했고, 그와 연관된 일들도 다 했으니 자기 할 일은 다 끝난 것이다(고로 프로그램도 끝난다).
따라서 WaitGroup을 써서 각 goroutine들이 다 완료될 때까지 기다려야 의도했던 결과를 얻을 수 있다.
2. 즉시 실행시키는 익명함수에 인자를 직접 넣어주지 않으면 어떻게 되는가?
for _, u := range urls {
wg.Add(1)
go func(){
defer wg.Done()
Crawl(u, depth-1, fetcher, cp)
}()
}
어차피 u에 접근할 수 있으니 굳이 인자로 넣어주지 않아도 돌아가지 않을까?
그러면 안 된다. u를 인자로 넣어줘야 의도했던 대로 작동한다.
프로그램 입장에서는 for문을 실행하면서 각 루프마다 goroutine을 생성해서 익명함수를 실행하라고 명령을 할 뿐이다.
실제 각 goroutine이 실행될 때 Crawl에 인자로 넘기는 u는 goroutine이 실행되는 시점의 u이기 때문에, urls의 순서대로 u가 각 goroutine에 전달된다는 보장이 없다.
예를 들어, 세 번째 루프의 goroutine이 다섯 번째 루프가 돌아가는 와중에 실행될 수도 있다는 것이다.
그리고 저 익명 함수는 클로져이기도 하다.
클로져는 자신의 body 밖에 있는 변수를 참조하는 함수다.
(참고: https://tour.golang.org/moretypes/25)
따라서 우리의 의도대로 작동시키려면 명시적으로 u를 익명함수에 인자로 넣어줘야 한다.