2014-11-24 12 views
6

Tôi muốn biết cách thành ngữ để giải quyết vấn đề này (mà hiện đang ném một lỗi bế tắc), các nhánh đệ quy một số lần không xác định, vì vậy tôi không thể đơn giản đóng kênh.Goroutines đệ quy, cách gọn gàng nhất để nói với Go là dừng đọc từ kênh là gì?

http://play.golang.org/p/avLf_sQJj_

tôi đã làm cho nó làm việc, bằng cách thông qua một con trỏ đến một con số, và incrementing nó, và tôi đã nhìn vào sử dụng waitgroups Sync. Tôi không cảm thấy (và tôi có thể sai), rằng tôi đã nghĩ ra một giải pháp thanh lịch. Các ví dụ Go tôi đã thấy có xu hướng đơn giản, thông minh và súc tích.

Đây là cuộc tập trận kéo dài từ một tour của Go, https://tour.golang.org/#73

Bạn có biết 'làm thế nào một lập trình viên Go' sẽ quản lý này? Bất kỳ trợ giúp sẽ được đánh giá cao. Tôi đang cố học tốt ngay từ đầu.

Trả lời

2

Thay vì liên quan đến sync.WaitGroup, bạn có thể mở rộng kết quả đang được gửi trên url được phân tích cú pháp và bao gồm số lượng URL mới được tìm thấy. Trong vòng lặp chính của bạn, bạn sẽ tiếp tục đọc kết quả miễn là có thứ gì đó để thu thập.

Trong trường hợp số url của bạn được tìm thấy sẽ là số lần xuất hiện thường trình, nhưng không nhất thiết phải là. Cá nhân tôi sẽ sinh ra nhiều hoặc ít cố định các thói quen tìm nạp, vì vậy bạn không mở quá nhiều yêu cầu HTTP (hoặc ít nhất bạn có quyền kiểm soát nó). Vòng lặp chính của bạn sẽ không thay đổi sau đó, vì nó không quan tâm cách tìm nạp đang được thực thi. Thực tế quan trọng ở đây là bạn cần gửi kết quả hoặc lỗi cho mỗi url - Tôi đã sửa đổi mã ở đây, vì vậy nó không sinh ra các thói quen mới khi độ sâu đã là 1.

Tác dụng phụ của giải pháp này là bạn có thể dễ dàng in tiến trình trong vòng lặp chính của bạn.

Dưới đây là ví dụ về sân chơi:

http://play.golang.org/p/BRlUc6bojf

package main 

import (
    "fmt" 
) 

type Fetcher interface { 
    // Fetch returns the body of URL and 
    // a slice of URLs found on that page. 
    Fetch(url string) (body string, urls []string, err error) 
} 

type Res struct { 
    url string 
    body string 
    found int // Number of new urls found 
} 

// Crawl uses fetcher to recursively crawl 
// pages starting with url, to a maximum of depth. 
func Crawl(url string, depth int, fetcher Fetcher, ch chan Res, errs chan error, visited map[string]bool) { 
    body, urls, err := fetcher.Fetch(url) 
    visited[url] = true 
    if err != nil { 
     errs <- err 
     return 
    } 

    newUrls := 0  
    if depth > 1 { 
     for _, u := range urls { 
      if !visited[u] { 
       newUrls++ 
       go Crawl(u, depth-1, fetcher, ch, errs, visited) 
      } 
     } 
    } 

    // Send the result along with number of urls to be fetched 
    ch <- Res{url, body, newUrls} 

    return 
} 

func main() { 
    ch := make(chan Res) 
    errs := make(chan error) 
    visited := map[string]bool{} 
    go Crawl("http://golang.org/", 4, fetcher, ch, errs, visited) 
    tocollect := 1 
    for n := 0; n < tocollect; n++ { 
     select { 
     case s := <-ch: 
      fmt.Printf("found: %s %q\n", s.url, s.body) 
      tocollect += s.found 
     case e := <-errs: 
      fmt.Println(e) 
     } 
    } 

} 

// fakeFetcher is Fetcher that returns canned results. 
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) 
} 

// fetcher is a populated fakeFetcher. 
var fetcher = fakeFetcher{ 
    "http://golang.org/": &fakeResult{ 
     "The Go Programming Language", 
     []string{ 
      "http://golang.org/pkg/", 
      "http://golang.org/cmd/", 
     }, 
    }, 
    "http://golang.org/pkg/": &fakeResult{ 
     "Packages", 
     []string{ 
      "http://golang.org/", 
      "http://golang.org/cmd/", 
      "http://golang.org/pkg/fmt/", 
      "http://golang.org/pkg/os/", 
     }, 
    }, 
    "http://golang.org/pkg/fmt/": &fakeResult{ 
     "Package fmt", 
     []string{ 
      "http://golang.org/", 
      "http://golang.org/pkg/", 
     }, 
    }, 
    "http://golang.org/pkg/os/": &fakeResult{ 
     "Package os", 
     []string{ 
      "http://golang.org/", 
      "http://golang.org/pkg/", 
     }, 
    }, 
} 

Và vâng, hãy làm theo @jimt tư vấn và thực hiện truy cập vào các chủ đề an toàn bản đồ.

+0

Tôi đã đánh dấu đây là câu trả lời, bởi vì trong khi tôi nghĩ rằng giải pháp @jimt cung cấp là thanh lịch và dạy rất nhiều công cụ hữu ích, điều này được thực hiện mà không dựa vào các công cụ vượt ra ngoài phạm vi của các hướng dẫn trước. Tôi đã yêu cầu 'câu trả lời chính xác' thành ngữ, tuy nhiên, và khi tôi không đủ kinh nghiệm, tôi không biết đó là cái tôi muốn đánh dấu cho điều đó. (bị cám dỗ chấp nhận cả hai câu trả lời) – SamMorrowDrums

2

Đây là cách diễn giải của tôi về bài tập. Có rất nhiều người thích nó, nhưng đây là của tôi. Tôi sử dụng sync.WaitGroup và bản đồ tùy chỉnh, được bảo vệ bằng mutex để lưu trữ các URL đã truy cập. Chủ yếu là vì loại tiêu chuẩn của loại map của Go không phải là chủ đề an toàn. Tôi cũng kết hợp các kênh dữ liệu và lỗi vào một cấu trúc duy nhất, trong đó có một phương pháp thực hiện việc đọc các kênh đã nói. Chủ yếu là để tách mối quan tâm và (cho là) ​​giữ mọi thứ sạch hơn một chút.

Ví dụ on playground:

package main 

import (
    "fmt" 
    "sync" 
) 

type Fetcher interface { 
    // Fetch returns the body of URL and 
    // a slice of URLs found on that page. 
    Fetch(url string) (body string, urls []string, err error) 
} 

// Crawl uses fetcher to recursively crawl 
// pages starting with url, to a maximum of depth. 
func Crawl(wg *sync.WaitGroup, url string, depth int, fetcher Fetcher, cache *UrlCache, results *Results) { 
    defer wg.Done() 

    if depth <= 0 || !cache.AtomicSet(url) { 
     return 
    } 

    body, urls, err := fetcher.Fetch(url) 
    if err != nil { 
     results.Error <- err 
     return 
    } 

    results.Data <- [2]string{url, body} 

    for _, url := range urls { 
     wg.Add(1) 
     go Crawl(wg, url, depth-1, fetcher, cache, results) 
    } 
} 

func main() { 
    var wg sync.WaitGroup 
    cache := NewUrlCache() 

    results := NewResults() 
    defer results.Close() 

    wg.Add(1) 
    go Crawl(&wg, "http://golang.org/", 4, fetcher, cache, results) 
    go results.Read() 
    wg.Wait() 
} 

// Results defines channels which yield results for a single crawled URL. 
type Results struct { 
    Data chan [2]string // url + body. 
    Error chan error  // Possible fetcher error. 
} 

func NewResults() *Results { 
    return &Results{ 
     Data: make(chan [2]string, 1), 
     Error: make(chan error, 1), 
    } 
} 

func (r *Results) Close() error { 
    close(r.Data) 
    close(r.Error) 
    return nil 
} 

// Read reads crawled results or errors, for as long as the channels are open. 
func (r *Results) Read() { 
    for { 
     select { 
     case data := <-r.Data: 
      fmt.Println(">", data) 

     case err := <-r.Error: 
      fmt.Println("e", err) 
     } 
    } 
} 

// UrlCache defines a cache of URL's we've already visited. 
type UrlCache struct { 
    sync.Mutex 
    data map[string]struct{} // Empty struct occupies 0 bytes, whereas bool takes 1 bytes. 
} 

func NewUrlCache() *UrlCache { return &UrlCache{data: make(map[string]struct{})} } 

// AtomicSet sets the given url in the cache and returns false if it already existed. 
// 
// All within the same locked context. Modifying a map without synchronisation is not safe 
// when done from multiple goroutines. Doing a Exists() check and Set() separately will 
// create a race condition, so we must combine both in a single operation. 
func (c *UrlCache) AtomicSet(url string) bool { 
    c.Lock() 
    _, ok := c.data[url] 
    c.data[url] = struct{}{} 
    c.Unlock() 
    return !ok 
} 

// fakeFetcher is Fetcher that returns canned results. 
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) 
} 

// fetcher is a populated fakeFetcher. 
var fetcher = fakeFetcher{ 
    "http://golang.org/": &fakeResult{ 
     "The Go Programming Language", 
     []string{ 
      "http://golang.org/pkg/", 
      "http://golang.org/cmd/", 
     }, 
    }, 
    "http://golang.org/pkg/": &fakeResult{ 
     "Packages", 
     []string{ 
      "http://golang.org/", 
      "http://golang.org/cmd/", 
      "http://golang.org/pkg/fmt/", 
      "http://golang.org/pkg/os/", 
     }, 
    }, 
    "http://golang.org/pkg/fmt/": &fakeResult{ 
     "Package fmt", 
     []string{ 
      "http://golang.org/", 
      "http://golang.org/pkg/", 
     }, 
    }, 
    "http://golang.org/pkg/os/": &fakeResult{ 
     "Package os", 
     []string{ 
      "http://golang.org/", 
      "http://golang.org/pkg/", 
     }, 
    }, 
} 

này chưa được thử nghiệm rộng rãi, vì vậy có lẽ có optimisations và bản sửa lỗi có thể được áp dụng, nhưng nó ít nhất nên cung cấp cho bạn một số ý tưởng.

+0

Cảm ơn rất nhiều @jimt, đó là một ví dụ thú vị. Vì vậy, câu trả lời cơ bản trong câu hỏi trên là sync.WaitGroup. Tôi đã nghĩ rằng sẽ có một giải pháp chỉ liên quan đến những điều được dạy trong các slide trước, nhưng trừ khi bất cứ ai có bất kỳ sự khôn ngoan nào, tôi nghĩ WaitGroups là con đường để đi. Một số lời khuyên tốt đẹp khác trong đó, tôi đã thực sự quên về trì hoãn, đó là thực sự hữu ích. Tôi sẽ không đánh dấu điều này là câu trả lời, chỉ để cho người khác cơ hội, nhưng đó chắc chắn là một câu trả lời tuyệt vời! – SamMorrowDrums

+0

bạn sẽ nói câu trả lời của bạn thêm 'thành ngữ' trong một ứng dụng Go sản xuất, sau đó là câu trả lời của Tomasz? Tôi không thích đánh dấu nhiều câu trả lời, nhưng tín dụng mà nó đến hạn. – SamMorrowDrums

Các vấn đề liên quan