3

我在 Ubuntu 13.04 上使用 go 1.1 devel

go version devel +ebe8bca920ad Wed May 15 15:34:47 2013 +1000 linux/386

根据http://golang.org/doc/faq#goroutines

当协程阻塞时,例如通过调用阻塞系统调用,运行时会自动将同一操作系统线程上的其他协程移动到不同的可运行线程,这样它们就不会被阻塞。

我正在尝试编写一个下载器,它可以使用 goroutine 分块下载一个大文件,这是我想出的最好的 goroutine:

func download(uri string, chunks chan int, offset int, file *os.file) {
    for current := range chunks {

        fmt.println("downloading range: ", current, "-", current+offset)

        client := &http.client{}
        req, _ := http.newrequest("get", uri, nil)
        req.header.set("range: ", fmt.sprintf("bytes=%d-%d", current, current+offset))
        resp, err := client.do(req)
        if err != nil {
            panic(err)
        }
        defer resp.body.close()
        body, err := ioutil.readall(resp.body)
        if err != nil {
            panic(err)
        }
        file.write(body)
    }
}

完整的脚本可在https://github.com/tuxcanfly/godown/blob/master/godown.go获得

即使文件正在正确下载和保存,我可以看到第二个块仅在第一个块完成时才开始。

分块下载不应该并行运行,还是我做错了什么?

4

1 回答 1

17

你只有一个 goroutine 下载块。

第 64 行:

go download(*download_url, chunks, offset, file)

你可能想要的是:

for i := 0; i < *threads; i++ {
    go download(*download_url, chunks, offset, file)
}

这将立即下载*threads块。


在并发工作之后,您可能会注意到第 29 行并没有按您的预期工作。如果块 1 在块 2 之前完成,则这些部分将被乱序写入。您可能想改用http://golang.org/pkg/os/#File.WriteAt


您的 Range 标头也有两个问题。

  1. 你不下载其余的。如果文件大小为 3002 并且您有 3 个线程,它将请求 0-1000、1000-2000、2000-3000,并且永远不会下载最后 2 个字节。
  2. 字节范围包括在内。这意味着您(正如您在前面的示例中看到的)两次下载一些字节。字节 1000 和 2000 被请求两次。当然,只要你写到正确的位置,你应该不会有太大的问题。

将第 19 行从

req.Header.Set("Range: ", fmt.Sprintf("bytes=%d-%d", current, current+offset))

对此

req.Header.Set("Range: ", fmt.Sprintf("bytes=%d-%d", current, current+offset-1))

有关 Range 标头的更多信息,我建议阅读RFC2616 中的第 14.35 节

于 2013-05-20T05:38:10.833 回答