Как подождать, пока все горуты не закончатся, не используя время.

Этот код выбирает все xml файлы в одной и той же папке, так как вызывается исполняемый файл и асинхронно применяет обработку к каждому результату в методе обратного вызова (в приведенном ниже примере просто распечатывается имя файла).

Как избежать использования метода сна, чтобы основной метод не мог выйти из системы? У меня проблемы с обволакиванием каналов (я предполагаю, что это нужно, чтобы синхронизировать результаты), поэтому любая помощь оценивается!

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

Ответ 1

Вы можете использовать sync.WaitGroup. Цитирование связанного примера:

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

Ответ 2

WaitGroups - это, безусловно, канонический способ сделать это. Однако, ради полноты, здесь было предложено решение, которое обычно использовалось перед WaitGroups. Основная идея состоит в том, чтобы использовать канал, чтобы сказать "Я сделан", и у вас есть главный горутин, пока каждая порожденная программа не сообщит о его завершении.

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

Ответ 3

sync.WaitGroup может помочь вам здесь.

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

Ответ 4

Хотя sync.waitGroup (sync.waitGroup) является каноническим способом продвижения вперед, он требует от вас выполнения хотя бы некоторых из ваших вызовов wg.Add до того, как вы wg.Wait для всех. Это может быть неосуществимо для простых вещей, таких как веб-сканер, где вы не знаете заранее количество рекурсивных вызовов и требуется время, чтобы извлечь данные, которые wg.Add вызовами wg.Add. В конце концов, вам нужно загрузить и проанализировать первую страницу, прежде чем вы узнаете размер первой партии дочерних страниц.

Я написал решение с использованием каналов, избегая waitGroup в своем решении - упражнении " Tour of Go" - веб-сканер. Каждый раз, когда запускается одна или несколько подпрограмм, вы отправляете номер на children канал. Каждый раз, когда процедура go собирается завершиться, вы отправляете 1 на done канал. Когда сумма детей равна сумме сделано, мы сделали.

Моя единственная оставшаяся проблема - жестко заданный размер канала results, но это (текущее) ограничение 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
            }
        }
    }
}

Полный исходный код для решения

Ответ 5

Вот решение, которое использует WaitGroup.

Сначала определите 2 служебных метода:

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

Затем замените вызов callback:

go callback(fileName)

С вызовом вашей служебной функции:

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

Последний шаг, добавьте эту строку в конце вашего main, а не вашего sleep. Это будет гарантировать, что основной поток ожидает завершения всех подпрограмм, прежде чем программа может остановиться.

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