6 votes

Télécharger des fichiers par morceaux dans plusieurs threads avec Golang

J'ai besoin de télécharger des fichiers, morceau par morceau, en plusieurs fois. Par exemple, j'ai 1k fichiers, chaque fichier ~100Mb-1Gb et je ne peux télécharger ces fichiers que par morceaux de 4096Kb (chaque requête http get me donne seulement 4kb).

Je veux donc les télécharger, disons, dans 20 threads (un thread pour un fichier) et je dois également télécharger quelques morceaux dans chacun de ces threads, simultanément.

Existe-t-il un exemple qui illustre une telle logique ?

7voto

reticentroot Points 2279

Voici un exemple de mise en place d'un téléchargeur concurrent. Les éléments à prendre en compte sont la bande passante, la mémoire et l'espace disque. Vous pouvez détruire votre bande passante en essayant d'en faire trop à la fois, et il en va de même pour la mémoire. Vous téléchargez des fichiers assez volumineux, la mémoire peut donc être un problème. Une autre chose à noter est qu'en utilisant les gorountines, vous perdez l'ordre des requêtes. Ainsi, si l'ordre des octets retournés a de l'importance, cela ne fonctionnera pas car vous devrez connaître l'ordre des octets pour assembler le fichier à la fin, ce qui signifierait qu'un téléchargement un par un est préférable, à moins que vous n'implémentiez un moyen de garder une trace de l'ordre (peut-être une sorte de map[order int][]bytes globale avec mutex pour éviter les conditions de course). Une alternative qui n'implique pas Go (en supposant que vous ayez une machine Unix pour plus de facilité) est d'utiliser Curl voir ici 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()

}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X