2015-01-11 417 views
2

我有下面的代碼,獲取URL的列表,然後有條件地下載文件並將其保存到文件系統。這些文件被同時提取,並且主goroutine等待所有文件被提取。但是,在完成所有請求後,程序從不退出(並且沒有錯誤)。golang sync.waitgroup永遠不會完成

我認爲正在發生的是,不知何故在WaitGroup走套路的量或者通過太多的增加,開始與(通過Add)或不足夠的遞減(一Done呼叫沒有發生)。

有什麼我明顯做錯了嗎?我將如何檢查WaitGroup中目前有多少個例程,以便我可以更好地調試發生了什麼?

package main 

import (
    "fmt" 
    "io" 
    "io/ioutil" 
    "net/http" 
    "os" 
    "strings" 
    "sync" 
) 

func main() { 
    links := parseLinks() 

    var wg sync.WaitGroup 

    for _, url := range links { 
     if isExcelDocument(url) { 
      wg.Add(1) 
      go downloadFromURL(url, wg) 
     } else { 
      fmt.Printf("Skipping: %v \n", url) 
     } 
    } 
    wg.Wait() 
} 

func downloadFromURL(url string, wg sync.WaitGroup) error { 
    tokens := strings.Split(url, "/") 
    fileName := tokens[len(tokens)-1] 
    fmt.Printf("Downloading %v to %v \n", url, fileName) 

    content, err := os.Create("temp_docs/" + fileName) 
    if err != nil { 
     fmt.Printf("Error while creating %v because of %v", fileName, err) 
     return err 
    } 

    resp, err := http.Get(url) 
    if err != nil { 
     fmt.Printf("Could not fetch %v because %v", url, err) 
     return err 
    } 
    defer resp.Body.Close() 

    _, err = io.Copy(content, resp.Body) 
    if err != nil { 
     fmt.Printf("Error while saving %v from %v", fileName, url) 
     return err 
    } 

    fmt.Printf("Download complete for %v \n", fileName) 

    defer wg.Done() 
    return nil 
} 

func isExcelDocument(url string) bool { 
    return strings.HasSuffix(url, ".xlsx") || strings.HasSuffix(url, ".xls") 
} 

func parseLinks() []string { 
    linksData, err := ioutil.ReadFile("links.txt") 
    if err != nil { 
     fmt.Printf("Trouble reading file: %v", err) 
    } 

    links := strings.Split(string(linksData), ", ") 

    return links 
} 
+1

你推遲'wg.Done()',但你仍然有它在函數的末尾。在它有機會從錯誤中返回之前,嘗試將它移到開頭。 –

+5

將一個指向'wg'的指針傳遞給該函數,而不是結構本身。 – plusmid

回答

20

有兩個問題與此代碼。首先,您必須將指向WaitGroup的指針傳遞到downloadFromURL(),否則該對象將被複制,並且Done()main()中將不可見。

參見:

func main() { 
    ... 
    go downloadFromURL(url, &wg) 
    ... 
} 

其次,defer wg.Done()應在downloadFromURL()第一語句之一,否則,如果您,也不會得到「註冊」該聲明之前,從函數返回,並不會得到調用。

func downloadFromURL(url string, wg *sync.WaitGroup) error { 
    defer wg.Done() 
    ... 
} 
+0

這個伎倆!謝謝@Bartosz – srt32

+0

指針繁榮。解決了我的問題並學到了新東西。謝謝! :) – C4u

3

Go中的參數總是按值傳遞。可以修改參數時使用指針。此外,請確保您始終執行wg.Done()。例如,

package main 

import (
    "fmt" 
    "io" 
    "io/ioutil" 
    "net/http" 
    "os" 
    "strings" 
    "sync" 
) 

func main() { 
    links := parseLinks() 

    wg := new(sync.WaitGroup) 

    for _, url := range links { 
     if isExcelDocument(url) { 
      wg.Add(1) 
      go downloadFromURL(url, wg) 
     } else { 
      fmt.Printf("Skipping: %v \n", url) 
     } 
    } 
    wg.Wait() 
} 

func downloadFromURL(url string, wg *sync.WaitGroup) error { 
    defer wg.Done() 
    tokens := strings.Split(url, "/") 
    fileName := tokens[len(tokens)-1] 
    fmt.Printf("Downloading %v to %v \n", url, fileName) 

    content, err := os.Create("temp_docs/" + fileName) 
    if err != nil { 
     fmt.Printf("Error while creating %v because of %v", fileName, err) 
     return err 
    } 

    resp, err := http.Get(url) 
    if err != nil { 
     fmt.Printf("Could not fetch %v because %v", url, err) 
     return err 
    } 
    defer resp.Body.Close() 

    _, err = io.Copy(content, resp.Body) 
    if err != nil { 
     fmt.Printf("Error while saving %v from %v", fileName, url) 
     return err 
    } 

    fmt.Printf("Download complete for %v \n", fileName) 

    return nil 
} 

func isExcelDocument(url string) bool { 
    return strings.HasSuffix(url, ".xlsx") || strings.HasSuffix(url, ".xls") 
} 

func parseLinks() []string { 
    linksData, err := ioutil.ReadFile("links.txt") 
    if err != nil { 
     fmt.Printf("Trouble reading file: %v", err) 
    } 

    links := strings.Split(string(linksData), ", ") 

    return links 
} 
0

正如@Bartosz提到的,您將需要一個參考傳遞給您的WaitGroup對象。他做了很好的工作,討論的重要性defer ws.Done()

我喜歡WaitGroup的簡單性。但是,我不喜歡我們需要將參考傳遞給goroutine,因爲這意味着併發邏輯將與您的業務邏輯混合在一起。

所以我想出了這個通用的功能來解決這個問題對我來說:

// Parallelize parallelizes the function calls 
func Parallelize(functions ...func()) { 
    var waitGroup sync.WaitGroup 
    waitGroup.Add(len(functions)) 

    defer waitGroup.Wait() 

    for _, function := range functions { 
     go func(copy func()) { 
      defer waitGroup.Done() 
      copy() 
     }(function) 
    } 
} 

所以,你的例子可以用此方式解決:

func main() { 
    links := parseLinks() 

    functions := []func(){} 
    for _, url := range links { 
     if isExcelDocument(url) { 
      function := func(url string){ 
       return func() { downloadFromURL(url) } 
      }(url) 

      functions = append(functions, function) 
     } else { 
      fmt.Printf("Skipping: %v \n", url) 
     } 
    } 

    Parallelize(functions...) 
} 

func downloadFromURL(url string) { 
    ... 
} 

如果你想使用它,你可以在這裏找到它https://github.com/shomali11/util