[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}
}