我有以下代码来获取URL的列表,然后有条件地下载文件并将其保存到文件系统。同时获取文件,主goroutine等待获取所有文件。但是,在完成所有请求后,程序永不退出(并且没有错误)。
我认为正在发生的事情是,WaitGroup
中的例行程序的数量要么增加太多而不能开始(通过Add
)或者没有减少足够的数量({{1}调用没有发生)。
有什么我显然做错了吗?我如何检查Done
中目前有多少例程,以便我可以更好地调试正在发生的事情?
WaitGroup
答案 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
找到它