在 Go 中的多线程中按块下载文件
Download files by chunks in multiple threads in Go
我需要在多线程中逐块下载文件。
例如,我有 1k 个文件,每个文件 ~100Mb-1Gb,我只能按 4096Kb 的块下载这些文件(每个 http get 请求只给我 4kb)。
在一个线程中下载它可能会很长时间,所以我想下载它们,假设在 20 个线程中(一个线程一个文件)我还需要在每个线程中下载几个块, 同时.
有没有例子可以说明这种逻辑?
这是一个如何设置并发下载器的例子。需要注意的是带宽、内存和磁盘 space。您可以通过尝试一次做很多事情来消耗带宽,内存也是如此。您下载的文件很大,因此内存可能是个问题。另一件需要注意的事情是,通过使用 gorountines 你会失去请求顺序。因此,如果返回字节的顺序很重要,那么这将不起作用,因为您最终必须知道 assemble 文件的字节顺序,这意味着最好一次下载一个,除非您实现了一种跟踪顺序的方法(可能是某种带有互斥锁的全局映射[order int][]字节以防止竞争条件)。不涉及 Go
的替代方法(假设您有一台简单的 unix 机器)是使用 Curl
参见此处 http://osxdaily.com/2014/02/13/download-with-curl/
package main
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"sync"
)
// now your going to have to be careful because you can potentially run out of memory downloading to many files at once..
// however here is an example that can be modded
func downloader(wg *sync.WaitGroup, sema chan struct{}, fileNum int, URL string) {
sema <- struct{}{}
defer func() {
<-sema
wg.Done()
}()
client := &http.Client{Timeout: 10}
res, err := client.Get(URL)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
var buf bytes.Buffer
// I'm copying to a buffer before writing it to file
// I could also just use IO copy to write it to the file
// directly and save memory by dumping to the disk directly.
io.Copy(&buf, res.Body)
// write the bytes to file
ioutil.WriteFile(fmt.Sprintf("file%d.txt", fileNum), buf.Bytes(), 0644)
return
}
func main() {
links := []string{
"url1",
"url2", // etc...
}
var wg sync.WaitGroup
// limit to four downloads at a time, this is called a semaphore
limiter := make(chan struct{}, 4)
for i, link := range links {
wg.Add(1)
go downloader(&wg, limiter, i, link)
}
wg.Wait()
}
您可以在 aws-go-sdk 中查看实现。
https://github.com/aws/aws-sdk-go-v2/blob/main/feature/s3/manager/download.go
- 创建
n
个并发go例程
- 下载
n
go routine 中的块
- 使用
writer.WriteAt()
查找并写入特定位置。
我需要在多线程中逐块下载文件。 例如,我有 1k 个文件,每个文件 ~100Mb-1Gb,我只能按 4096Kb 的块下载这些文件(每个 http get 请求只给我 4kb)。
在一个线程中下载它可能会很长时间,所以我想下载它们,假设在 20 个线程中(一个线程一个文件)我还需要在每个线程中下载几个块, 同时.
有没有例子可以说明这种逻辑?
这是一个如何设置并发下载器的例子。需要注意的是带宽、内存和磁盘 space。您可以通过尝试一次做很多事情来消耗带宽,内存也是如此。您下载的文件很大,因此内存可能是个问题。另一件需要注意的事情是,通过使用 gorountines 你会失去请求顺序。因此,如果返回字节的顺序很重要,那么这将不起作用,因为您最终必须知道 assemble 文件的字节顺序,这意味着最好一次下载一个,除非您实现了一种跟踪顺序的方法(可能是某种带有互斥锁的全局映射[order int][]字节以防止竞争条件)。不涉及 Go
的替代方法(假设您有一台简单的 unix 机器)是使用 Curl
参见此处 http://osxdaily.com/2014/02/13/download-with-curl/
package main
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"sync"
)
// now your going to have to be careful because you can potentially run out of memory downloading to many files at once..
// however here is an example that can be modded
func downloader(wg *sync.WaitGroup, sema chan struct{}, fileNum int, URL string) {
sema <- struct{}{}
defer func() {
<-sema
wg.Done()
}()
client := &http.Client{Timeout: 10}
res, err := client.Get(URL)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
var buf bytes.Buffer
// I'm copying to a buffer before writing it to file
// I could also just use IO copy to write it to the file
// directly and save memory by dumping to the disk directly.
io.Copy(&buf, res.Body)
// write the bytes to file
ioutil.WriteFile(fmt.Sprintf("file%d.txt", fileNum), buf.Bytes(), 0644)
return
}
func main() {
links := []string{
"url1",
"url2", // etc...
}
var wg sync.WaitGroup
// limit to four downloads at a time, this is called a semaphore
limiter := make(chan struct{}, 4)
for i, link := range links {
wg.Add(1)
go downloader(&wg, limiter, i, link)
}
wg.Wait()
}
您可以在 aws-go-sdk 中查看实现。 https://github.com/aws/aws-sdk-go-v2/blob/main/feature/s3/manager/download.go
- 创建
n
个并发go例程 - 下载
n
go routine 中的块
- 使用
writer.WriteAt()
查找并写入特定位置。