猿问

golang sync.WaitGroup 永远不会完成

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


我认为正在发生的事情是,不知何故, go 例程的WaitGroup数量要么增加了太多(via Add),要么没有减少足够多(Done没有发生调用)。


有什么我明显做错了吗?我将如何检查当前有多少 go 例程,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

}



浮云间
浏览 231回答 3
3回答

慕后森

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

青春有我

Go 中的参数总是按值传递。当参数可能被修改时使用指针。另外,请确保您始终执行wg.Done()。例如,package mainimport (    "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}
随时随地看视频慕课网APP

相关分类

Go
我要回答