🔥 동시성을 활용한 웹 크롤러 만들기
759자
8분
강의 목차
Go 언어는 동시성 프로그래밍을 위한 강력한 기능들을 제공합니다. 이번 예제에서는 Go의 동시성 기능을 활용하여 웹 크롤러를 병렬로 처리하는 방법에 대해 알아보겠습니다.
먼저, Crawl
함수를 수정하여 URL을 병렬로 가져오되, 같은 URL을 두 번 가져오지 않도록 해보겠습니다.
func Crawl(url string, depth int, fetcher Fetcher) { if depth <= 0 { return } // 이미 가져온 URL인지 확인하기 위해 맵을 사용합니다. visited := make(map[string]bool) // 작업을 동기화하기 위해 뮤텍스를 사용합니다. var mu sync.Mutex // 작업 그룹을 생성하여 고루틴을 관리합니다. var wg sync.WaitGroup // 재귀 호출 대신 큐를 사용하여 URL을 저장합니다. queue := []string{url} for len(queue) > 0 { // 큐에서 URL을 꺼냅니다. url := queue[0] queue = queue[1:] // 이미 방문한 URL인 경우 건너뜁니다. mu.Lock() if visited[url] { mu.Unlock() continue } visited[url] = true mu.Unlock() // 작업 그룹에 작업을 추가합니다. wg.Add(1) // 고루틴을 생성하여 URL을 가져옵니다. go func(url string) { defer wg.Done() body, urls, err := fetcher.Fetch(url) if err != nil { fmt.Println(err) return } fmt.Printf("found: %s %q\n", url, body) // 새로 찾은 URL을 큐에 추가합니다. mu.Lock() for _, u := range urls { if !visited[u] { queue = append(queue, u) } } mu.Unlock() }(url) } // 모든 작업이 완료될 때까지 기다립니다. wg.Wait() }
go
이제 코드를 하나씩 살펴보겠습니다.
visited := make(map[string]bool)
go
visited
맵을 사용하여 이미 가져온 URL을 추적합니다.- 맵의 키는 URL이고, 값은 해당 URL을 방문했는지 여부를 나타내는 불리언 값입니다.
var mu sync.Mutex
go
sync.Mutex
를 사용하여 맵에 대한 동시 접근을 동기화합니다.- 맵은 여러 고루틴에서 동시에 접근할 수 있으므로, 뮤텍스를 사용하여 경쟁 상태를 방지합니다.
var wg sync.WaitGroup
go
sync.WaitGroup
을 사용하여 생성된 고루틴들을 관리합니다.- 작업 그룹은 모든 고루틴이 완료될 때까지 기다리는 역할을 합니다.
queue := []string{url}
go
- 재귀 호출 대신 큐를 사용하여 URL을 저장합니다.
- 초기에는 시작 URL만 큐에 추가됩니다.
for len(queue) > 0 { url := queue[0] queue = queue[1:] // ... }
go
- 큐에 URL이 있는 동안 반복합니다.
- 큐에서 URL을 꺼내고, 해당 URL에 대한 작업을 수행합니다.
mu.Lock() if visited[url] { mu.Unlock() continue } visited[url] = true mu.Unlock()
go
- 뮤텍스를 사용하여
visited
맵에 대한 접근을 동기화합니다. - 이미 방문한 URL인 경우 건너뜁니다.
- 방문하지 않은 URL인 경우
visited
맵에 추가합니다.
wg.Add(1)
go
- 작업 그룹에 작업을 추가합니다.
wg.Add(1)
은 작업 그룹에 새로운 작업이 추가되었음을 알립니다.
go func(url string) { defer wg.Done() // ... }(url)
go
- 고루틴을 생성하여 URL을 가져옵니다.
defer wg.Done()
은 고루틴이 완료되면 작업 그룹에 알립니다.- 고루틴 내에서 URL을 가져오고, 결과를 출력합니다.
mu.Lock() for _, u := range urls { if !visited[u] { queue = append(queue, u) } } mu.Unlock()
go
- 새로 찾은 URL을 큐에 추가합니다.
- 뮤텍스를 사용하여
visited
맵과 큐에 대한 접근을 동기화합니다. - 방문하지 않은 URL만 큐에 추가합니다.
wg.Wait()
go
- 모든 작업이 완료될 때까지 기다립니다.
wg.Wait()
은 모든 고루틴이 완료될 때까지 블로킹합니다.
이렇게 수정된 Crawl
함수는 URL을 병렬로 가져오면서도 같은 URL을 두 번 가져오지 않도록 합니다. 고루틴을 사용하여 동시성을 활용하고, 뮤텍스와 작업 그룹을 사용하여 동기화와 관리를 수행합니다.
전체 코드
package main import ( "fmt" "sync" ) type Fetcher interface { Fetch(url string) (body string, urls []string, err error) } func Crawl(url string, depth int, fetcher Fetcher) { if depth <= 0 { return } visited := make(map[string]bool) var mu sync.Mutex var wg sync.WaitGroup queue := []string{url} for len(queue) > 0 { url := queue[0] queue = queue[1:] mu.Lock() if visited[url] { mu.Unlock() continue } visited[url] = true mu.Unlock() wg.Add(1) go func(url string) { defer wg.Done() body, urls, err := fetcher.Fetch(url) if err != nil { fmt.Println(err) return } fmt.Printf("found: %s %q\n", url, body) mu.Lock() for _, u := range urls { if !visited[u] { queue = append(queue, u) } } mu.Unlock() }(url) } wg.Wait() } func main() { Crawl("<https://golang.org/>", 4, fetcher) } type fakeFetcher map[string]*fakeResult type fakeResult struct { body string urls []string } func (f fakeFetcher) Fetch(url string) (string, []string, error) { if res, ok := f[url]; ok { return res.body, res.urls, nil } return "", nil, fmt.Errorf("not found: %s", url) } var fetcher = fakeFetcher{ "<https://golang.org/>": &fakeResult{ "The Go Programming Language", []string{ "<https://golang.org/pkg/>", "<https://golang.org/cmd/>", }, }, "<https://golang.org/pkg/>": &fakeResult{ "Packages", []string{ "<https://golang.org/>", "<https://golang.org/cmd/>", "<https://golang.org/pkg/fmt/>", "<https://golang.org/pkg/os/>", }, }, "<https://golang.org/pkg/fmt/>": &fakeResult{ "Package fmt", []string{ "<https://golang.org/>", "<https://golang.org/pkg/>", }, }, "<https://golang.org/pkg/os/>": &fakeResult{ "Package os", []string{ "<https://golang.org/>", "<https://golang.org/pkg/>", }, }, }
go
이 코드는 Go 언어의 동시성 기능을 활용하여 웹 크롤러를 병렬로 처리하는 예제입니다. Crawl
함수를 수정하여 URL을 병렬로 가져오면서도 같은 URL을 두 번 가져오지 않도록 했습니다. 고루틴, 뮤텍스, 작업 그룹을 사용하여 동시성을 제어하고 동기화를 수행했죠.
이렇게 Go 언어의 동시성 기능을 활용하면 효율적이고 빠른 웹 크롤러를 만들 수 있습니다. 병렬 처리를 통해 크롤링 속도를 높이고, 동기화 기술을 사용하여 안전하게 데이터를 처리할 수 있죠.