同时下载同一文件多次

时间:2019-09-04 20:23:07

标签: go concurrency download

我正在同时从一片配置对象(每个配置对象包含需要下载的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。)

运行代码时,它重复下载相同的文件,而不是下载每个文件,并且它打印到控制台的信息(从Downloading file:to location:行)完全相同。对于每个对象(而不是打印每个对象唯一的消息),这绝对是并发问题。

这个问题使我想起了如果您尝试使用闭包运行for loop并最终将单个对象实例锁定到循环中并在同一对象上重复执行,会发生什么情况。

是什么原因导致这种现象,以及如何解决?

1 个答案:

答案 0 :(得分:2)

我很确定你的猜测

  

这个问题使我想起了如果您尝试使用闭包运行for循环并最终将单个对象实例锁定到您的循环中并在同一对象上重复执行时会发生什么。

是正确的。简单的解决方法是“分配给本地变量”,例如

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用法删除。