Go Go - 3
14 Mar 2021 | Go오늘은 서버에서 데이터를 불러오고, 가장 기본적인 Go 루틴을 공부했다.
HTTP
HTTP 통신을 사용하기 쉽게 net/http
모듈이 존재한다.
import (
"net/http"
)
func getResponse(url string) *http.Response {
resp, err := http.Get(url)
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
return nil
}
return resp
}
모듈단위로 분리해봤는데, 해당 url에서 response를 받는 함수이다.
중간에 에러처리는 정상적으로 response를 받지 못했으면 err
가 nil
이 아닌데, 이 때 예외처리를 해준다.
func readResBody(resp *http.Response) {
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: reading : %v\n", err)
os.Exit(1)
}
fmt.Printf("%s\n", b)
resp.Body.Close()
}
변수 b
에 byte[] 형태로 데이터를 저장한다.
다음 예외처리를 한 뒤 b
를 출력한 뒤, 메모리 누수를 막기 위해 resp.Body를 닫는다.
하지만 해당 코드는 b에 데이터를 많이 저장해야된다는 문제가 있는데…
func readResBody(resp *http.Response) {
io.Copy(os.Stdout, resp.Body)
resp.Body.Close()
}
io 모듈의 Copy 메서드로 표준 출력장치에 resp.Body의 내용을 복사한다.
이러면 속도도 더 빠르게 출력할 수 있다.
go routine
고 루틴이라는게 뭔가 하면… 아직 나도 제대로 공부하지 못했지만
Thread의 단점을 보완하고 업그레이드한 비슷한 기능이라고 생각하자.
Go의 동시성 프로그래밍 철학을 가장 잘 보여주는 기능인 것 같다.
고 루틴은 어떻게 동작하는가?
이 글을 보고 이해가 잘 되었다.
아무튼, 위의 fetch 함수를 고 루틴을 사용해 돌려봤다.
func fetchAll() {
ch := make(chan string) // 문자열의 채널을 만듬
for _, url := range os.Args[2:] {
go fetch(url, ch) // go routine
}
for range os.Args[2:] {
fmt.Println(<-ch)
}
}
make
함수를 이용해서 문자열의 채널을 만든다.
이로써 go routine들이 공유하는 채널을 만든다. 쓰레드에서 공유하는 메모리 영역과 비슷하다.
func fetch(url string, ch chan<-string){
resp, err := http.Get(url)
nbytes, err := io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
ch <- fmt.Sprintf("%7d %s", nbytes, url)
}
ioutil.Discard
는, 출력의 한 종류인데 그냥 데이터가 필요 없을 때 사용한다. 위의 코드는 읽는 시간만 필요하니..
다음 채널에 바이트의 수와 url을 집어넣고, 함수를 끝낸다.
이러면 쓰레드와 같은 느낌으로 go routine이 돌아가는데, 훨씬 빠르고 가볍다.