用goroutines下载文件?

时间:2015-10-01 04:28:24

标签: http go goroutine

我是Go的新手,我正在学习如何使用goroutines。

我有一个下载图片的功能:

func imageDownloader(uri string, filename string) {
    fmt.Println("starting download for ", uri)

    outFile, err := os.Create(filename)
    defer outFile.Close()
    if err != nil {
        os.Exit(1)
    }

    client := &http.Client{}

    req, err := http.NewRequest("GET", uri, nil)

    resp, err := client.Do(req)
    defer resp.Body.Close()

    if err != nil {
        panic(err)
    }

    header := resp.ContentLength
    bar := pb.New(int(header))
    rd := bar.NewProxyReader(resp.Body)
    // and copy from reader
    io.Copy(outFile, rd)
}

当我自己调用另一个函数时,它会完全下载图像并且没有截断的数据。

然而,当我尝试修改它以使其成为goroutine时,图像经常被截断或零长度文件。

func imageDownloader(uri string, filename string, wg *sync.WaitGroup) {
    ...
    io.Copy(outFile, rd)
    wg.Done()
}

func main() {
var wg sync.WaitGroup
wg.Add(1)
go imageDownloader(url, file, &wg)
wg.Wait()
}

我是否错误地使用了WaitGroups?什么可能导致这种情况,我该如何解决?

更新

解决了它。我已将wg.add()函数放在循环之外。 :(

3 个答案:

答案 0 :(得分:3)

虽然我不确定究竟是什么导致了您的问题,但这里有两个选项可以让它恢复正常工作。

首先,从同步库中查看example of how to use waitgroups,尝试在函数开头调用defer wg.Done(),以确保即使goroutine意外结束,等待组也会正确递减。

其次,io.Copy会返回您未检查的错误。无论如何,这不是很好的练习,但在你的特殊情况下,它会阻止你看到复制例程中是否确实存在错误。检查并妥善处理。它还返回写入的字节数,这对您也有帮助。

答案 1 :(得分:3)

您的示例在使用WaitGroups时没有任何明显错误。只要您使用与您启动的goroutine数量相同的数字调用wg.Add(),或者每次启动新的goroutine时将其递增1,那就应该是正确的。

但是,对于goroutine中的某些错误条件,您可以调用os.Exitpanic,因此如果您运行了多个错误条件,则其中任何一个运行失败都会终止所有错误,无论如何使用WaitGroups。如果它在没有恐慌信息的情况下失败,我会看一下os.Exit(1)行。

在函数开始时使用defer wg.Done()也是一种很好的做法,这样即使发生错误,goroutine仍然会减少其计数器。这样,如果其中一个goroutines返回错误,你的主线程就不会挂起。

答案 2 :(得分:0)

当您defer时,我在您的示例中进行的一项更改是使用Done。我认为这个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 imageDownloader(uri string, filename string) {
    ...
    io.Copy(outFile, rd)
}

func main() {
    functions := []func(){}
    list := make([]Object, 5)
    for _, object := range list {
        function := func(obj Object){ 
            imageDownloader(object.uri, object.filename) 
        }(object)
        functions = append(functions, function)
    }

    Parallelize(functions...)        

    fmt.Println("Done")
}

如果您想使用它,可以在https://github.com/shomali11/util

找到它