go - 同时多次下载同一文件

标签 go concurrency download

我同时从配置对象 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/

相关文章:

正则表达式匹配字符串中的手机号码

go - 调试golang可执行文件时无法在vscode中获取局部变量

go - 为什么我的 GOPATH/src 刚安装后包含几个目录?

concurrency - Centos 7 : Running "Grunt Serve" (concurrent) task 上的 Ionic "concurrent:server"错误

algorithm - 如何(准确)估计剩余下载时间?

javascript/golang RSA 加密/验证

java - 如果一个对象不再发生变化,它是否可以安全地跨线程共享? (斯卡拉/ java )

swift - 如何为等待函数调用添加超时

iphone - Objective-C 检查下载文件的大小

perl - 提交表单后,如何使用 WWW::Mechanize 下载文件?