프로그래밍

A Tour of Go Exercise - Web Crawler 풀이

2kindsofcs 2020. 2. 11. 20:39

튜토리얼 자체에서는 WaitGroup에 대한 설명은 없는데, WaitGroup을 써서 풀었다.

출제자는 아마 채널을 이용해서 풀기를 의도했던 게 아닐까 싶다.

 

코드는 여기서 실행해볼 수 있다.

https://play.golang.org/p/Rm6J-yLHR5j

 

The Go Playground

package main import ( "fmt" "sync" ) type Fetcher interface { // Fetch returns the body of URL and // a slice of URLs found on that page. Fetch(url string) (body string, urls []string, err error) } // Crawl uses fetcher to recursively crawl // pages starti

play.golang.org

 

		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를 익명함수에 인자로 넣어줘야 한다. 

반응형