CastleJo의 개발일지

Go Go - 3

|

오늘은 서버에서 데이터를 불러오고, 가장 기본적인 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를 받지 못했으면 errnil이 아닌데, 이 때 예외처리를 해준다.

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이 돌아가는데, 훨씬 빠르고 가볍다.