GO

[Go]URL Checker 프로젝트 따라하기(goroutines)

[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}
}
최신글