¿Cómo esperar a que todos los goroutines terminen sin usar el tiempo? ¿Dormir?

Este código selecciona todos los archivos XML en la misma carpeta, ya que el ejecutable invocado y el procesamiento asíncrono se aplica a cada resultado en el método de devolución de llamada (en el siguiente ejemplo, solo se imprime el nombre del archivo).

¿Cómo evito usar el método de suspensión para evitar que el método principal salga? Tengo problemas para envolver mi cabeza alrededor de los canales (supongo que eso es lo que se necesita para sincronizar los resultados), ¡así que aprecio cualquier ayuda!

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

Respuestas a la pregunta(4)

Su respuesta a la pregunta