[Go]URL Checker 프로젝트 따라하기(goroutines)
Goroutines 이해하기
Top-down 방식의 프로그래밍
일반적인 프로그래밍은 탑다운 방식의 프로그래밍이다. 코드를 위에서부터 차례대로 실행한다.
package main import ( "fmt" "time" ) func main() { sexyCount("choi") sexyCount("dahye") } func sexyCount(person string) { for i := 0; i < 10; i++ { fmt.Println(person, " is sexy", i) time.Sleep(time.Second) } }
gorontines 방식의 프로그래밍
하지만 goruoutines를 사용하면 탑다운 방식으로 코드가 실행되는 것이 아닌 동시에 코드를 실행할 수 있다.
func main() { go sexyCount("choi") sexyCount("dahye") }
goroutines 생명주기
단, goroutines는 main 함수가 실행되는 동안에만 유효하다.
func main() { go sexyCount("choi") go sexyCount("dahye") }
마지막에 5초 기다리는 코드를 작성하면 5초후에 main 함수가 끝나는 것을 확인할 수 있다.
func main() { go sexyCount("choi") go sexyCount("dahye") time.Sleep((time.Second * 5)) }
Channel 메세지 전달
gorountines와 main 함수(혹은 goroutines) 간의 커뮤니케이션을 하기 위해서 channel이라는 메세지 전달 기능을 사용한다.
package main import ( "time" ) func main() { people := [2]string{"dahye", "choi"} for _, person := range people { go isSexy(person) } time.Sleep((time.Second * 5)) } func isSexy(person string) bool { time.Sleep(time.Second * 5) return true }
위 코드와 같이 main 함수가 종료되어 코드가 실행되지 않는 경우에 channel을 사용할 수 있다. channel은 함수에 인수를 하나 더 받아서 화살표를 사용하여 메세지를 전달할 수 있다. 이때 주의할 점은 전달받은 메세지를 main 함수에서 받아줄 필요가 있다는 것이다.
package main import ( "fmt" "time" ) func main() { c := make(chan bool) people := [2]string{"dahye", "choi"} for _, person := range people { go isSexy(person, c) } // channel로 전달된 메세지를 받는다. -> Sleep 함수를 지워도 대기중 fmt.Println(<-c) fmt.Println(<-c) } func isSexy(person string, c chan bool) { time.Sleep(time.Second * 5) fmt.Println(person) // channel로 메세지를 전달한다. c <- true }
goroutines의 생명주기가 끝난 후 메세지를 받으면 다음과 같이 deadlock 에러가 난다.
package main import ( "fmt" "time" ) func main() { c := make(chan string) people := [2]string{"dahye", "choi"} for _, person := range people { go isSexy(person, c) } // channel로 전달된 메세지를 받는다. -> Sleep 함수를 지워도 대기중(Blocking Operation) resultOne := <-c resultTwo := <-c resultThree := <-c fmt.Println("Waiting for messages : ") fmt.Println("Received this messages : ", resultOne) fmt.Println("Received this messages : ", resultTwo) fmt.Println("Received this messages : ", resultThree) } func isSexy(person string, c chan string) { time.Sleep(time.Second * 10) // channel로 메세지를 전달한다. c <- person + " is sexy" }
그렇다면 배열의 개수를 모를 경우에는 어떻게 해야할까? 다음과 같이 for 반복문을 사용하여 필요한만큼 메세지를 받을 수 있다.
package main import ( "fmt" "time" ) func main() { c := make(chan string) people := [5]string{"dahye", "choi", "sai", "kei", "yama"} for _, person := range people { go isSexy(person, c) } // channel로 전달된 메세지를 받는다. -> Sleep 함수를 지워도 대기중(Blocking Operation) for i := 0; i < len(people); i++ { fmt.Println("waiting for ", i) fmt.Println(<-c) } } func isSexy(person string, c chan string) { time.Sleep(time.Second * 10) // channel로 메세지를 전달한다. c <- person + " is sexy" }
URL Checker 만들기
package main import ( "errors" "fmt" "net/http" ) // 요청에 대한 결과값을 구조체를 만들어 담는다. type RequestResult struct { url string status string } // 에러 메세지를 변수에 담아 재활용할 수 있게 만든다. var errRequestFailed = errors.New("Request Failed") func main() { // 결과 값을 map 함수로 만들어 key-value 형태의 변수를 만든다. results := make(map[string]string) // channel을 사용하여 메세지를 담을 변수를 만든다. c := make(chan RequestResult) // URL Checker에서 사용할 URL을 배열에 담는다. urls := []string{ "https://www.airbnb.com/", "https://www.google.com/", "https://www.amazon.com/", "https://www.reddit.com/", "https://www.google.com/", "https://soundcloud.com/", "https://www.facebook.com/", "https://www.instagram.com/", "https://academy.nomadcoders.co/", } // 배열 안에 담긴 URL을 반복문으로 돌린다. 이때 URL은 goroutines를 사용하여 동시에 코드를 실행한다. for _, url := range urls { go hitURL(url, c) } // 메세지는 goroutines의 개수만큼 받아야한다. 일일이 개수를 세지 않고 URL의 개수만큼 반복문을 돌려 메세지를 받는다. for i := 0; i < len(urls); i++ { result := <-c results[result.url] = result.status } // URL Checking 후의 결과 값을 url, status 형태로 출력한다. for url, status := range results { fmt.Println(url, status) } } // RequestResult 라는 이름으로 메세지를 받는다(channel) func hitURL(url string, c chan<- RequestResult) { resp, err := http.Get(url) status := "OK" // status 기본값이 'OK'이고 에러가 났을때만 'FAILED'를 출력한다. if err != nil || resp.StatusCode >= 400 { fmt.Println(err, resp.StatusCode) status = "FAILED" } // status가 'OK'라면 key-value 형태의 데이터를 메시지로 메인에 전달한다. c <- RequestResult{url: url, status: status} }