[Go] 4. URL Checker & Go routines & Channel

URL Checker 구현

기본 URL 체크 함수

package main

import (
	"errors"
	"fmt"
	"net/http"
)

var errRequestFailed = errors.New("Request failed")

func main() {
	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/",
	}
	for _, url := range urls {
		hitURL(url)
	}
}

func hitURL(url string) error {
	fmt.Println("Checking:", url)
	resp, err := http.Get(url)
	if err == nil || resp.StatusCode >= 400 {
		return errRequestFailed
	}
	return nil
}

 

출력 결과

Checking: https://www.airbnb.com/
Checking: https://www.google.com/
Checking: https://www.amazon.com/
Checking: https://www.reddit.com/
Checking: https://www.google.com/
Checking: https://soundcloud.com/
Checking: https://www.facebook.com/
Checking: https://www.instagram.com/
Checking: https://academy.nomadcoders.co/

 


 

slow url checker

Map에 결과를 저장하면서 URL을 체크해보자.

 

주의: nil map 에러

var results map[string]string
results["hello"] = "Hello"  // panic: assignment to entry in nil map

 

Map을 사용하기 전에 반드시 초기화해야 한다.

// 방법 1
var results = map[string]string{}

// 방법 2
var results = make(map[string]string)

 

완성된 코드

package main

import (
	"errors"
	"fmt"
	"net/http"
)

var errRequestFailed = errors.New("Request failed")

func main() {
	var results = make(map[string]string)

	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/",
	}

	for _, url := range urls {
		result := "OK"
		err := hitURL(url)
		if err != nil {
			result = "FAILED"
		}
		results[url] = result
	}

	for url, result := range results {
		fmt.Println(url, result)
	}
}

func hitURL(url string) error {
	fmt.Println("Checking:", url)
	resp, err := http.Get(url)
	if err != nil || resp.StatusCode >= 400 {
		fmt.Println(err, resp.StatusCode)
		return errRequestFailed
	}
	return nil
}

 

출력 결과

Checking: https://www.airbnb.com/
Checking: https://www.google.com/
Checking: https://www.amazon.com/
Checking: https://www.reddit.com/
Checking: https://www.google.com/
Checking: https://soundcloud.com/
Checking: https://www.facebook.com/
Checking: https://www.instagram.com/
Checking: https://academy.nomadcoders.co/
https://www.facebook.com/ OK
https://www.instagram.com/ OK
https://academy.nomadcoders.co/ OK
https://www.airbnb.com/ OK
https://www.google.com/ OK
https://www.amazon.com/ OK
https://www.reddit.com/ OK
https://soundcloud.com/ OK

 

이 방식의 문제는 URL을 순차적으로 체크한다는 점이다. 각 URL의 응답을 기다렸다가 다음 URL을 체크하므로 시간이 오래 걸린다.

 


 

Gorutines

Goroutine은 Go의 경량 스레드다. go 키워드를 함수 호출 앞에 붙이면 해당 함수가 별도의 goroutine에서 실행된다.

 

기본 Goroutine 예제

package main

import (
	"fmt"
	"time"
)

func main() {
	go countPerson("jihyun")
	countPerson("gildong")
}

func countPerson(person string) {
	for i := 0;i < 10; i++ {
		fmt.Println(person, i)
		time.Sleep(time.Second)
	}
}

 

출력 결과

gildong 0
jihyun 0
jihyun 1
gildong 1
jihyun 2
gildong 2
jihyun 3
gildong 3
gildong 4
jihyun 4
jihyun 5
gildong 5
gildong 6
jihyun 6
jihyun 7
gildong 7
gildong 8
jihyun 8
jihyun 9
gildong 9

 

두 함수가 동시에 실행되는 것을 확인할 수 있다.

 


 

Goroutine의 함정: Main 함수 종료

문제가 있는 코드

package main

import (
	"fmt"
	"time"
)

func main() {
	go countPerson("jihyun")
	go countPerson("gildong")
}

 

위 코드는 아무것도 출력하지 않는다. Main 함수가 즉시 종료되면 모든 goroutine도 함께 종료되기 때문이다.

 

임시 해결책: Sleep 사용

package main

import (
	"fmt"
	"time"
)

func main() {
	go countPerson("jihyun")
	go countPerson("gildong")
	time.Sleep(time.Second * 5) // 5초 동안 대기
}

 

출력 결과

gildong 0
jihyun 0
jihyun 1
gildong 1
jihyun 2
gildong 2
jihyun 3
gildong 3
jihyun 4
gildong 4

 

하지만 time.Sleep은 좋은 해결책이 아니다. Goroutine이 언제 끝날지 정확히 알 수 없기 때문이다.

 


 

Channels

Channel은 goroutine들 간의 통신 수단이다. 파이프라인 또는 소통 창구라고 생각하면 된다.

 

Channel 생성

c := make(chan bool) // bool 타입을 전송하는 채널

 


 

문제점 - Goroutine 대기 없이 종료

 

package main

import (
	"fmt"
	"time"
)

func main() {
	// make channel
	c := make(chan bool)

	people := [2]string{"jihyun", "gildong"}
	for _, person := range people {
		go isFriend(person, c)
	}
	time.Sleep(time.Second * 10)
}

// isFriend: 5초 뒤 true라는 메시지를 보내줌
func isFriend(person string, c chan bool) {
	time.Sleep(time.Second * 5)
	c <- true
}

 

위 코드는 time.Sleep으로 대기하지만, 여전히 좋은 방법이 아니다.

 

 


 

Blocking Operation - 채널에서 값 받기

<- c는 blocking operation이다. 채널로부터 메시지를 받을 때까지 main 함수가 대기한다.

package main

import (
	"fmt"
	"time"
)

func main() {
	// make channel
	c := make(chan bool)

	people := [2]string{"jihyun", "gildong"}
	for _, person := range people {
		go isFriend(person, c)
	}
	result := <- c
	fmt.Println(result)
}

// isFriend: 5초 뒤 true라는 메시지를 보내줌
func isFriend(person string, c chan bool) {
	time.Sleep(time.Second * 5)
	c <- true
}

 

출력 결과

true

 


 

여러 개의 값 받기

package main

import (
	"fmt"
	"time"
)

func main() {
	// make channel
	c := make(chan bool)

	people := [2]string{"jihyun", "gildong"}
	for _, person := range people {
		go isFriend(person, c)
	}
	fmt.Println(<- c)
	fmt.Println(<- c)
}

// isFriend: 5초 뒤 true라는 메시지를 보내줌
func isFriend(person string, c chan bool) {
	time.Sleep(time.Second * 5)
	fmt.Println(person)
	c <- true
}

 

출력 결과

jihyun
gildong
true
true

 


 

Deadlock 에러

채널에서 받으려는 값의 개수가 실제로 전송되는 값보다 많으면 deadlock이 발생한다.

func main() {
	c := make(chan bool)

	people := [2]string{"jihyun", "gildong"}
	for _, person := range people {
		go isFriend(person, c)
	}
	fmt.Println(<- c)
	fmt.Println(<- c)
	fmt.Println(<- c) // 3번째 값은 없음!
}

 

gildong
true
jihyun
true
fatal error: all goroutines are asleep - deadlock!

 


 

String 타입 채널

package main

import (
	"fmt"
	"time"
)

func main() {
	// make channel
	c := make(chan string)
	people := [2]string{"jihyun", "gildong"}

	for _, person := range people {
		go isFriend(person, c)
	}
	fmt.Println("Waiting for messages")
	result1 := <- c
	result2 := <- c
	fmt.Println("Received this message:", result1)
	fmt.Println("Received this message:", result2)
}

// isFriend: 5초 뒤 true라는 메시지를 보내줌
func isFriend(person string, c chan string) {
	time.Sleep(time.Second * 10)
	c <- person + " is my friend"
}

 

출력 결과

Waiting for messages
Received this message: gildong is my friend
Received this message: jihyun is my friend

 


 

URL Checker & Goroutines & Channels

이제 URL Checker를 goroutine과 channel을 사용해 동시에 실행되도록 개선해보자.

 

Struct를 사용한 결과 전달

package main

import (
	"errors"
	"fmt"
	"net/http"
)

type requestResult struct {
	url string
	status string
}

var errRequestFailed = errors.New("Request failed")

func main() {
	results := make(map[string]string)
	c := make(chan requestResult)

	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/",
	}

	for _, url := range urls {
		go hitURL(url, c)
	}

	for i := 0; i < len(urls); i++ {
		result := <- c
		results[result.url] = result.status
	}

	for url, status := range results {
		fmt.Println(url, status)
	}
}

func hitURL(url string, c chan<- requestResult) {
	resp, err := http.Get(url)
	status := "OK"
	if err != nil || resp.StatusCode >= 400 {
		status = "FAILED"
	}
	c <- requestResult{url: url, status: status}
}

 

출력 결과

{https://www.reddit.com/ OK}
{https://www.amazon.com/ OK}
{https://www.facebook.com/ OK}
{https://www.instagram.com/ OK}
{https://soundcloud.com/ OK}
{https://www.airbnb.com/ OK}
{https://www.google.com/ OK}
{https://www.google.com/ OK}
{https://academy.nomadcoders.co/ OK}

 


 

채널 방향 지정

chan<-send-only channel을 의미한다. 이 함수는 채널에 값을 보내기만 하고 받을 수 없다.

- chan<-: send-only (보내기만 가능)
- <-chan: receive-only (받기만 가능)
- chan: 양방향 (보내기/받기 모두 가능)

 


 

url checker와 url checker & goroutine & channel의 시간 차이

 

 


 

핵심 정리

- Goroutine: go 키워드로 함수를 동시에 실행
- Channel: Goroutine 간 통신을 위한 파이프라인
- Blocking Operation: <- c는 값을 받을 때까지 대기
- 동시성: 여러 URL을 동시에 체크하여 전체 실행 시간 단축

 

 

아래 강의를 통해 학습한 내용을 바탕으로 작성했습니다.

https://nomadcoders.co/go-for-beginners

 

Go 프로그래밍 언어 무료 강의

Channel, Goroutines 등 Go의 핵심 개념을 익히고, 간단한 스크래핑 서비스를 만들며 실습 위주로 기초부터 배웁니다. 17,000명이 선택한 인기 무료 입문 강의로, 지금 Go 입문을 시작하세요!

nomadcoders.co

 

https://go.dev/doc/#references

 

Documentation - The Go Programming Language

Documentation The Go programming language is an open source project to make programmers more productive. Go is expressive, concise, clean, and efficient. Its concurrency mechanisms make it easy to write programs that get the most out of multicore and netwo

go.dev

 

'Language > Go' 카테고리의 다른 글

[Go] 3. struct, method, dictionary  (0) 2025.11.08
[Go] 2. pointer, map, arrays  (0) 2025.11.08
[Go] 1. package, function, for, if, switch  (0) 2025.11.07