web-dev-qa-db-fra.com

Comment attendre que toutes les goroutines se terminent sans utiliser time.Sleep?

Ce code sélectionne tous les fichiers XML dans le même dossier, car l'exécutable appelé applique un traitement asynchrone à chaque résultat dans la méthode de rappel (dans l'exemple ci-dessous, seul le nom du fichier est imprimé).

Comment éviter d'utiliser la méthode sleep pour empêcher la méthode principale de sortir? J'ai du mal à comprendre les canaux (je suppose que c'est ce qu'il faut pour synchroniser les résultats), alors toute aide est la bienvenue!

package main

import (
    "fmt"
    "io/ioutil"
    "path"
    "path/filepath"
    "os"
    "runtime"
    "time"
)

func eachFile(extension string, callback func(file string)) {
    exeDir := filepath.Dir(os.Args[0])
    files, _ := ioutil.ReadDir(exeDir)
    for _, f := range files {
            fileName := f.Name()
            if extension == path.Ext(fileName) {
                go callback(fileName)
            }
    }
}


func main() {
    maxProcs := runtime.NumCPU()
    runtime.GOMAXPROCS(maxProcs)

    eachFile(".xml", func(fileName string) {
                // Custom logic goes in here
                fmt.Println(fileName)
            })

    // This is what i want to get rid of
    time.Sleep(100 * time.Millisecond)
}
88
Dante

Vous pouvez utiliser sync.WaitGroup . Citant l'exemple lié:

package main

import (
        "net/http"
        "sync"
)

func main() {
        var wg sync.WaitGroup
        var urls = []string{
                "http://www.golang.org/",
                "http://www.google.com/",
                "http://www.somestupidname.com/",
        }
        for _, url := range urls {
                // Increment the WaitGroup counter.
                wg.Add(1)
                // Launch a goroutine to fetch the URL.
                go func(url string) {
                        // Decrement the counter when the goroutine completes.
                        defer wg.Done()
                        // Fetch the URL.
                        http.Get(url)
                }(url)
        }
        // Wait for all HTTP fetches to complete.
        wg.Wait()
}
140
zzzz

WaitGroups est définitivement le moyen canonique de le faire. Juste pour être complet, voici la solution couramment utilisée avant l’introduction de WaitGroups. L'idée de base est d'utiliser un canal pour dire "J'ai terminé" et de laisser le goroutine principal attendre que chaque routine générée ait signalé son achèvement.

func main() {
    c := make(chan struct{}) // We don't need any data to be passed, so use an empty struct
    for i := 0; i < 100; i++ {
        go func() {
            doSomething()
            c <- struct{}{} // signal that the routine has completed
        }()
    }

    // Since we spawned 100 routines, receive 100 messages.
    for i := 0; i < 100; i++ {
        <- c
    }
}
50
joshlf

sync.WaitGroup peut vous aider ici.

package main

import (
    "fmt"
    "sync"
    "time"
)


func wait(seconds int, wg * sync.WaitGroup) {
    defer wg.Done()

    time.Sleep(time.Duration(seconds) * time.Second)
    fmt.Println("Slept ", seconds, " seconds ..")
}


func main() {
    var wg sync.WaitGroup

    for i := 0; i <= 5; i++ {
        wg.Add(1)   
        go wait(i, &wg)
    }
    wg.Wait()
}
3
dimmg

Bien que sync.waitGroup (wg) est la voie à suivre canonique, il vous oblige à faire au moins une partie de votre wg.Add appelle avant vous wg.Wait pour tout compléter. Cela peut ne pas être faisable pour des choses simples comme un robot d'indexation Web, où vous ne connaissez pas le nombre d'appels récursifs à l'avance et où il faut un certain temps pour récupérer les données qui gèrent le wg.Add appels. Après tout, vous devez charger et analyser la première page avant de connaître la taille du premier lot de pages enfants.

J'ai écrit une solution en utilisant des canaux, en évitant waitGroup dans ma solution, l'exercice Tour of Go - crawler . Chaque fois qu'une ou plusieurs routines de lancement sont démarrées, vous envoyez le numéro au canal children. Chaque fois qu'une routine est sur le point de se terminer, vous envoyez un 1 au canal done. Lorsque la somme des enfants est égale à la somme de fait, nous avons terminé.

Ma dernière préoccupation concerne la taille codée en dur du canal results, mais il s'agit d'une limitation de Go (actuelle).


// recursionController is a data structure with three channels to control our Crawl recursion.
// Tried to use sync.waitGroup in a previous version, but I was unhappy with the mandatory sleep.
// The idea is to have three channels, counting the outstanding calls (children), completed calls 
// (done) and results (results).  Once outstanding calls == completed calls we are done (if you are
// sufficiently careful to signal any new children before closing your current one, as you may be the last one).
//
type recursionController struct {
    results  chan string
    children chan int
    done     chan int
}

// instead of instantiating one instance, as we did above, use a more idiomatic Go solution
func NewRecursionController() recursionController {
    // we buffer results to 1000, so we cannot crawl more pages than that.  
    return recursionController{make(chan string, 1000), make(chan int), make(chan int)}
}

// recursionController.Add: convenience function to add children to controller (similar to waitGroup)
func (rc recursionController) Add(children int) {
    rc.children <- children
}

// recursionController.Done: convenience function to remove a child from controller (similar to waitGroup)
func (rc recursionController) Done() {
    rc.done <- 1
}

// recursionController.Wait will wait until all children are done
func (rc recursionController) Wait() {
    fmt.Println("Controller waiting...")
    var children, done int
    for {
        select {
        case childrenDelta := <-rc.children:
            children += childrenDelta
            // fmt.Printf("children found %v total %v\n", childrenDelta, children)
        case <-rc.done:
            done += 1
            // fmt.Println("done found", done)
        default:
            if done > 0 && children == done {
                fmt.Printf("Controller exiting, done = %v, children =  %v\n", done, children)
                close(rc.results)
                return
            }
        }
    }
}

Code source complet pour la solution

1
dirkjot

Voici une solution qui utilise WaitGroup.

Tout d’abord, définissez 2 méthodes d’utilité:

package util

import (
    "sync"
)

var allNodesWaitGroup sync.WaitGroup

func GoNode(f func()) {
    allNodesWaitGroup.Add(1)
    go func() {
        defer allNodesWaitGroup.Done()
        f()
    }()
}

func WaitForAllNodes() {
    allNodesWaitGroup.Wait()
}

Ensuite, remplacez l'invocation de callback:

go callback(fileName)

Avec un appel à votre fonction utilitaire:

util.GoNode(func() { callback(fileName) })

Dernière étape, ajoutez cette ligne à la fin de votre main, au lieu de votre sleep. Cela garantira que le thread principal attend la fin de toutes les routines avant que le programme ne puisse s'arrêter.

func main() {
  // ...
  util.WaitForAllNodes()
}
0
gamliela