我同时从配置对象 slice (其中每个配置对象包含需要下载的 URL)下载文件(使用 WaitGroup),但是当我使用并发时,我会在每次执行时获得完全相同的数据。
我相信我包含了下面的所有内容作为一个最小的可重现示例。
这是我的导入:
package main
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"sync"
)
循环遍历我的对象并执行 go 例程来下载每个文件的方法如下:
func downloadAllFiles(configs []Config) {
var wg sync.WaitGroup
for i, config := range configs {
wg.Add(1)
go config.downloadFile(&wg)
}
wg.Wait()
}
基本上,我的功能是将文件从 URL 下载到存储在 NFS 上的目录中。
这是下载功能:
func (config *Config) downloadFile(wg *sync.WaitGroup) {
resp, _ := http.Get(config.ArtifactPathOrUrl)
fmt.Println("Downloading file: " + config.ArtifactPathOrUrl)
fmt.Println(" to location: " + config.getNfsFullFileSystemPath())
defer resp.Body.Close()
nfsDirectoryPath := config.getBaseNFSFileSystemPath()
os.MkdirAll(nfsDirectoryPath, os.ModePerm)
fullFilePath := config.getNfsFullFileSystemPath()
out, err := os.Create(fullFilePath)
if err != nil {
panic(err)
}
defer out.Close()
io.Copy(out, resp.Body)
wg.Done()
}
这是 Config 结构的最小部分:
type Config struct {
Namespace string `json:"namespace,omitempty"`
Tenant string `json:"tenant,omitempty"`
Name string `json:"name,omitempty"`
ArtifactPathOrUrl string `json:"artifactPathOrUrl,omitempty"`
}
以下是实例/辅助函数:
func (config *Config) getDefaultNfsURLBase() string {
return "http://example.domain.nfs.location.com/"
}
func (config *Config) getDefaultNfsFilesystemBase() string {
return "/data/nfs/location/"
}
func (config *Config) getBaseNFSFileSystemPath() string {
basePath := filepath.Dir(config.getNfsFullFileSystemPath())
return basePath
}
func (config *Config) getNfsFullFileSystemPath() string {
// basePath is like: /data/nfs/location/
trimmedBasePath := strings.TrimSuffix(config.getDefaultNfsFilesystemBase(), "/")
fileName := config.getBaseFileName()
return trimmedBasePath + "/" + config.Tenant + "/" + config.Namespace + "/" + config.Name + "/" + fileName
}
以下是我获取配置并解码它们的方法:
func getConfigs() string {
b, err := ioutil.ReadFile("pulsarDeploy_example.json")
if err != nil {
fmt.Print(err)
}
str := string(b) // convert content to a 'string'
return str
}
func deserializeJSON(configJson string) []Config {
jsonAsBytes := []byte(configJson)
configs := make([]Config, 0)
err := json.Unmarshal(jsonAsBytes, &configs)
if err != nil {
panic(err)
}
return configs
}
作为一个最小的示例,我认为 pulsarDeploy_example.json
文件的数据应该有效:
[{ "artifactPathOrUrl": "http://www.java2s.com/Code/JarDownload/sample/sample.jar.zip",
"namespace": "exampleNamespace1",
"name": "exampleName1",
"tenant": "exampleTenant1"
},
{
"artifactPathOrUrl": "http://www.java2s.com/Code/JarDownload/sample-calculator/sample-calculator-bundle-2.0.jar.zip",
"namespace": "exampleNamespace1",
"name": "exampleName2",
"tenant": "exampleTenant1"
},
{
"artifactPathOrUrl": "http://www.java2s.com/Code/JarDownload/helloworld/helloworld.jar.zip",
"namespace": "exampleNamespace1",
"name": "exampleName3",
"tenant": "exampleTenant1"
},
{
"artifactPathOrUrl": "http://www.java2s.com/Code/JarDownload/fabric-activemq/fabric-activemq-demo-7.0.2.fuse-097.jar.zip",
"namespace": "exampleNamespace1",
"name": "exampleName4",
"tenant": "exampleTenant1"
}
]
(请注意,示例文件 URL 只是我在网上抓取的随机 Jars。)
当我运行代码时,它不会下载每个文件,而是重复下载同一文件,并将信息打印到控制台(从 下载文件:
和 到位置:
行)对于每个对象都是完全相同的(而不是打印每个对象唯一的消息),这绝对是一个并发问题。
这个问题让我想起如果您尝试运行带有闭包的 for 循环
并最终将单个对象实例锁定到循环中并在同一个对象上重复执行,会发生什么情况。
是什么导致了这种行为?如何解决?
最佳答案
我很确定你的猜测
This issue reminds me of what happens if you try to run a for loop with a closure and end up locking a single object instance into your loop and executing repeatedly on the same object.
是正确的。简单的修复方法是“分配给本地变量”,例如
for _, config := range configs {
wg.Add(1)
cur := config
go cur.downloadFile(&wg)
}
但我不喜欢将 waitgroup 作为参数的 API,所以我建议
for _, config := range configs {
wg.Add(1)
go func(cur Config) {
defer wg.Done()
cur.downloadFile()
}(config)
}
并将 downloadFile
签名更改为 func (config *Config) downloadFile()
并删除其中的 wg
用法。
关于go - 同时多次下载同一文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57795207/