为了账号安全,请及时绑定邮箱和手机立即绑定

golang sync.WaitGroup 永远不会完成

golang sync.WaitGroup 永远不会完成

Go
浮云间 2021-09-10 18:15:58
我有以下代码,用于获取 URL 列表,然后有条件地下载文件并将其保存到文件系统。文件被并发获取,主 goroutine 等待所有文件被获取。但是,程序在完成所有请求后永远不会退出(并且没有错误)。我认为正在发生的事情是,不知何故, go 例程的WaitGroup数量要么增加了太多(via Add),要么没有减少足够多(Done没有发生调用)。有什么我明显做错了吗?我将如何检查当前有多少 go 例程,WaitGroup以便我可以更好地调试正在发生的事情?package mainimport (    "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}
查看完整描述

3 回答

?
慕后森

TA贡献1802条经验 获得超5个赞

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

    ...

}


查看完整回答
反对 回复 2021-09-10
?
青春有我

TA贡献1784条经验 获得超8个赞

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

}



查看完整回答
反对 回复 2021-09-10
  • 3 回答
  • 0 关注
  • 230 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信