Communicating through channels

As we've seen in earlier chapters, goroutines offer powerful but simple concurrent operation. Most of these examples have been generating output or responding to user requests, but long running processes are often generating data that needs to be utilized by the application. In this example, we see how a channel can be used to effectively gather data from multiple threads to aggregate and report.

Our example is a simple tool that'll get the disk usage of a directory. For each element within this directory, we'll start a goroutine (dirSize()) that'll calculate the space used by the directory and the files it contains. This function returns the result through a channel so the application can use the information once it's available:

package main

import (
"fmt"
"os"
"path/filepath"
)

type sizeInfo struct {
name string
size int64
}

func dirSize(path string, result chan sizeInfo) {
var size int64

filepath.Walk(path, func(_ string, file os.FileInfo, err error) error {
if err == nil {
size += file.Size()
}

return nil
})

result <- sizeInfo{filepath.Base(path), size}
}

Within the reportUsage() function, we start as many goroutines as there are files reported in the specified directory. The code then prints the usage result from each goroutine when it completes using for info := range result, and then terminates when every result is returned (if results == len(files) {break}), adding a simple total before we exit:

func reportUsage(path string) {
f, _ := os.Open(path)
files, _ := f.Readdir(-1)
f.Close()

result := make(chan sizeInfo)
for _, file := range files {
go dirSize(filepath.Join(path, file.Name()), result)
}

var total int64
results := 0
for info := range result {
total += info.size
fmt.Printf("%s: %d ", info.name, info.size)

results++
if results == len(files) {
break
}
}
fmt.Printf(" Total: %d ", total)
}

Lastly, we add a main() function that simply parses arguments to initialize the reportUsage() function. If no argument is specified, we'll report for the current directory reported by os.Getwd():

func main() {
path, _ := os.Getwd()

if len(os.Args) == 2 {
path = os.Args[1]
}

fmt.Println("Scanning", path)
reportUsage(path)
}

Running this example may return immediately, but if you invoke it on a large directory, it may take some time to complete. By doing this, you can see that each printed appears as soon as the related goroutine completes, and the total is always last to appear. The preceding listing doesn't include some boilerplate number formatting seen in the resulting screenshot (that can be found in this book's code repository):

Reporting the usage of a directory; typically smaller items appear first as they're faster to calculate
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.168.214