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 |