[英]golang sync.WaitGroup never completes
我有以下代碼來獲取URL的列表,然后有條件地下載文件並將其保存到文件系統。 同時獲取文件,主goroutine等待獲取所有文件。 但是,在完成所有請求后,程序永遠不會退出 (並且沒有錯誤)。
我認為正在發生的事情是, WaitGroup
例行程序的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
}
這段代碼有兩個問題。 首先,您必須將指向WaitGroup的指針傳遞給downloadFromURL()
,否則將復制該對象,並且在main()
不會顯示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 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
}
正如@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找到它
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.