golang sync.WaitGroup永远不会完成

时间:2015-01-11 23:26:25

标签: go

我有以下代码来获取URL的列表,然后有条件地下载文件并将其保存到文件系统。同时获取文件,主goroutine等待获取所有文件。但是,在完成所有请求后,程序永不退出(并且没有错误)。

我认为正在发生的事情是,WaitGroup中的例行程序的数量要么增加太多而不能开始(通过Add)或者没有减少足够的数量({{1}调用没有发生)。

有什么我显然做错了吗?我如何检查Done中目前有多少例程,以便我可以更好地调试正在发生的事情?

WaitGroup

3 个答案:

答案 0 :(得分:27)

此代码存在两个问题。首先,您必须将指向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()
    ...
}

答案 1 :(得分: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
}

答案 2 :(得分: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

找到它