177 votes

Comment attendre la fin de toutes les goroutines sans utiliser time.Sleep ?

Ce code sélectionne tous les fichiers xml dans le même dossier, comme l'exécutable invoqué et applique de manière asynchrone le traitement à 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 sortie de la méthode principale ? J'ai du mal à me faire une idée des 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)
}

273voto

zzzz Points 23017

Vous pouvez utiliser sync.Groupe d'attente . Je cite l'exemple en lien :

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()
}

91voto

joshlf Points 9622

Les groupes d'attente sont certainement la façon canonique de le faire. Cependant, par souci d'exhaustivité, voici la solution qui était couramment utilisée avant l'introduction des groupes d'attente. L'idée de base est d'utiliser un canal pour dire "J'ai terminé", et de faire attendre la goroutine principale jusqu'à ce que chaque routine engendré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
    }
}

19voto

dimmg Points 685

sync.Groupe d'attente 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()
}

4voto

dirkjot Points 651

Bien que sync.waitGroup (wg) est la voie canonique à suivre, mais elle exige que vous fassiez au moins une partie de votre travail d'écriture. wg.Add les appels avant vous wg.Wait pour que tous puissent la compléter. Cela peut ne pas être possible pour des choses simples, comme un robot d'exploration du Web, où l'on ne connaît pas le nombre d'appels récursifs à l'avance et où il faut un certain temps pour récupérer les données qui alimentent le processus d'exploration. 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 enfant.

J'ai écrit une solution en utilisant des canaux, en évitant waitGroup dans ma solution le Tour de Go - crawler web exercice. Chaque fois qu'une ou plusieurs go-routines sont lancées, vous envoyez le nombre à l'adresse suivante children canal. Chaque fois qu'une routine d'exécution est sur le point de s'achever, vous envoyez un message de type 1 a la done canal. Quand la somme des enfants est égale à la somme des faits, nous avons terminé.

Le seul problème qui subsiste est la taille codée en dur de l'unité de contrôle de l'unité de contrôle. results mais il s'agit d'une limitation (actuelle) du Go.

// 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 de la solution

4voto

gamliela Points 619

Voici une solution qui fait appel à 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 d'utilité :

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

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

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

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