Hello!

Today we will learn what is WaitGroup in golang and we will look how to apply them on an example of the program for files search.

WaitGroup is waiting for the completion of go routines. The main go routine calls the Add method, and then each go routine calls the Done method when the routine ends.

Let’s start with the findFile method. In this method we read all files and directories in the root folder. We loop through them and if the name of the file what we are looking for matches the name of the file from the loop then we add it to the list of matches. If it is a directory, then recursively call a search in it

package main

import (
    "fmt"
    "io/ioutil"
    "path/filepath"
    "strings"
)

var (
    matches []string
)

func findFile(root string, filename string) {
    fmt.Println("Searching in", root)
    files, _ := ioutil.ReadDir(root)
    for _, file := range files {
        if strings.Contains(file.Name(), filename) {
            matches = append(matches, filepath.Join(root, file.Name()))
        }
        if file.IsDir() {
            findFile(filepath.Join(root, file.Name()), filename)
        }
    }
}

func main() {
    findFile("/var/", "secret.txt")
    for _, file := range matches {
        fmt.Println("Matched", file)
    }
}

The search works, but it takes a long time. You can speed it up with go routines. To do this, add the word go before calling the findFile method and before the recursive call. But in this case, the search will not work because the script will exit immediately, because there is nothing to wait for the go routines. To fix this WaitGroup can be used. The script will only complete when all go routines call the Done method.

package main

import (
    "fmt"
    "io/ioutil"
    "path/filepath"
    "strings"
    "sync"
)

var (
    matches   []string
    waitGroup = sync.WaitGroup{}
    lock      = sync.Mutex{}
)

func findFile(root string, filename string) {
    fmt.Println("Searching in", root)
    files, _ := ioutil.ReadDir(root)
    for _, file := range files {
        if strings.Contains(file.Name(), filename) {
            lock.Lock()
            matches = append(matches, filepath.Join(root, file.Name()))
            lock.Unlock()
        }
        if file.IsDir() {
            waitGroup.Add(1)
            go findFile(filepath.Join(root, file.Name()), filename)
        }
    }
    waitGroup.Done()
}

func main() {
    waitGroup.Add(1)
    go findFile("/var/", "secret.txt")
    waitGroup.Wait()
    for _, file := range matches {
        fmt.Println("Matched", file)
    }
}

A few things have been added here. In the findFile method, lock was added before adding file names to list. To ensure that routines do not overwrite data. Another thing that has been added is waitGroup. Before the each go routine call, the waitGroup.Add(1) call is added, and after the go routine, waitGroup.Done() is called. And to the main method added waitGroup.Wait() which will wait while all go routines finish work. And then a list of all files will be displayed.