golang sync.WaitGroup永远不会完成

srt*_*t32 8 go

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

我认为正在发生的事情是,以某种方式,其中的例程量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
}
Run Code Online (Sandbox Code Playgroud)

Bar*_*osz 29

这段代码有两个问题.首先,您必须将指向WaitGroup的指针传递给downloadFromURL(),否则该对象将被复制并且Done()不会在其中可见main().

看到:

func main() {
    ...
    go downloadFromURL(url, &wg)
    ...
}
Run Code Online (Sandbox Code Playgroud)

第二,defer wg.Done()应该是第一个语句之一downloadFromURL(),否则如果你从该语句之前的函数返回,它将不会被"注册"而不会被调用.

func downloadFromURL(url string, wg *sync.WaitGroup) error {
    defer wg.Done()
    ...
}
Run Code Online (Sandbox Code Playgroud)