2016-03-02 76 views
0

我想寫去了一個簡單的web刮刀:golang:如何在所有goroutines完成後關閉頻道?

  • 從URL
  • 提取物得到一個模式的所有HREF一些具體領域
  • 和寫入到CSV文件

這裏是我的代碼:

package main 

import (
    "encoding/csv" 
    "flag" 
    "fmt" 
    "github.com/PuerkitoBio/goquery" 
    "log" 
    "net/http" 
    "net/url" 
    "os" 
    "strings" 
    "sync" 
) 

type Enterprise struct { 
    name  string 
    tax_code string 
    group string 
    capital string 
} 

var u, f string 
var name, tax_code, group, capital string 

func init() { 
    flag.StringVar(&u, "u", "", "Which URL to download from") 
    flag.StringVar(&f, "f", "", "Path to the csv file to write the output to") 
} 

func check(e error) { 
    if e != nil { 
     panic(e) 
    } 
} 

func findHrefs(u string) map[string]string { 
    resp, err := http.Get(u) 
    check(err) 

    doc, err := goquery.NewDocumentFromResponse(resp) 
    check(err) 

    e_hrefs := make(map[string]string) 
    doc.Find("td div a").Each(func(_ int, s *goquery.Selection) { 
     e_href, _ := s.Attr("href") 
     if strings.HasPrefix(e_href, "/Thong-tin-doanh-nghiep") && s.Text() != "" { 
      e_hrefs[e_href] = s.Text() 
     } 
    }) 
    return e_hrefs 
} 

func fetch(url string, name string, file *os.File, wg *sync.WaitGroup, c chan Enterprise) { 
    defer wg.Done() 

    log.Println("Fetching URL", url) 
    resp, err := http.Get(url) 
    check(err) 

    doc, err := goquery.NewDocumentFromResponse(resp) 
    check(err) 
    e := new(Enterprise) 
    doc.Find("td").Each(func(_ int, s *goquery.Selection) { 
     if s.Text() == "Mã số thuế:" { 
      e.tax_code = s.Next().Text() 
     } 
     if s.Text() == "Tên ngành cấp 2:" { 
      e.group = s.Next().Text() 
     } 
     if s.Text() == "Sở hữu vốn:" { 
      e.capital = s.Next().Text() 
     } 
    }) 
    w := csv.NewWriter(file) 
    w.Write([]string{name, "'" + e.tax_code, e.group, e.capital}) 
    w.Flush() 
    c <- *e 
} 

func getDoc(u, f string) { 
    parsedUrl, err := url.Parse(u) 
    check(err) 

    file, err := os.Create(f) 
    check(err) 
    defer file.Close() 

    var wg sync.WaitGroup 
    c := make(chan Enterprise) 

    e_hrefs := findHrefs(u) 
    for e_href, name := range e_hrefs { 
     wg.Add(1) 
     go fetch(parsedUrl.Scheme+"://"+parsedUrl.Host+e_href, name, file, &wg, c) 
    } 
    wg.Wait() 
} 

func main() { 
    flag.Parse() 
    if u == "" || f == "" { 
     fmt.Println("-u=<URL to download from> -f=<Path to the CSV file>") 
     os.Exit(1) 
    } 
    getDoc(u, f) 
} 

問題是通道沒有關閉畢竟夠程都完成,我必須按控制 + Ç讓我的shell提示符後面:

2016/03/02 09:34:05 Fetching URL ... 
2016/03/02 09:34:05 Fetching URL ... 
2016/03/02 09:34:05 Fetching URL ... 
^Csignal: interrupt 

通過閱讀this,我改變最後一行getDoc FUNC喜歡的東西:

go func() { 
    wg.Wait() 
    close(c) 
}() 

現在我可以讓運行時,我的shell提示符回來,但之前的所有夠程都完成,沒有寫入CSV文件中的通道被關閉。

我哪裏錯了?

+1

進程發送SIGQUIT找出的夠程都在等待。我沒有看到從該頻道收到的任何代碼。 'fetch'結束時,灌裝機是否卡住? –

回答

3

對我來說,它看起來並不像你從你的信讀,因爲它是同步信道(你從來沒有宣佈它的長度)如果它收到的值就會阻塞。所以,你需要通過value <- c從您的c閱讀或您的抓取功能會掛在c <- *e

這導致您sync.WaitGroup從不wg.Done()從未遞減計數器,它永遠不會導致wg.Wait()停止阻塞,從而導致你的close(c)永遠不會得到所謂

0

我原來的代碼是這樣的:

e_hrefs := findHrefs(u) 
w := csv.NewWriter(file) 
for e_href, name := range e_hrefs { 
    wg.Add(1) 
    go fetch(parsedUrl.Scheme+"://"+parsedUrl.Host+e_href, name, &wg, c) 
    e := <-c 
    w.Write([]string{name, "'" + e.tax_code, e.group, e.capital}) 
    w.Flush() 
} 
wg.Wait() 

,你可以看到,這是不是併發。

我剛剛固定使用range條款遍歷道:

e_hrefs := findHrefs(u) 
for e_href, name := range e_hrefs { 
    wg.Add(1) 
    go fetch(parsedUrl.Scheme+"://"+parsedUrl.Host+e_href, name, &wg, c) 
} 
go func() { 
    wg.Wait() 
    close(c) 
}() 

w := csv.NewWriter(file) 
for e := range c { 
    w.Write([]string{e.name, "'" + e.tax_code, e.group, e.capital}) 
    w.Flush() 
}