Solution 5: Go Concurrency
Explore how to apply Go's concurrency model by modifying worker routines to execute command-line tools concurrently. Learn to use goroutines, channels, and the os/exec package to run the wc tool for counting lines, words, and characters in text input within a concurrent system.
We'll cover the following...
Solution
To modify wPools.go so that each worker implements the functionality of wc(1), we can replace the calculation of the square with a call to the wc command-line tool using Go’s built-in os/exec package. ...
package main
import (
"fmt"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"sync"
"time"
)
type Client struct {
id int
text string
}
type Result struct {
job Client
lines int
words int
characters int
}
var size = runtime.GOMAXPROCS(0)
var clients = make(chan Client, size)
var data = make(chan Result, size)
func worker(wg *sync.WaitGroup) {
for c := range clients {
cmd := exec.Command("wc", "-l", "-w", "-c")
cmd.Stdin = strings.NewReader(c.text)
out, err := cmd.Output()
if err != nil {
fmt.Println(err)
continue
}
output := strings.Fields(string(out))
lines, _ := strconv.Atoi(output[0])
lines++
words, _ := strconv.Atoi(output[1])
characters, _ := strconv.Atoi(output[2])
res := Result{c, lines, words, characters}
data <- res
time.Sleep(time.Second)
}
wg.Done()
}
func create(n int) {
for i := 0; i < n; i++ {
c := Client{i, "Hello World!"}
clients <- c
}
close(clients)
}
func main() {
if len(os.Args) != 3 {
fmt.Println("Need #jobs and #workers!")
return
}
nJobs, err := strconv.Atoi(os.Args[1])
if err != nil {
fmt.Println(err)
return
}
nWorkers, err := strconv.Atoi(os.Args[2])
if err != nil {
fmt.Println(err)
return
}
go create(nJobs)
finished := make(chan interface{})
go func() {
for d := range data {
fmt.Printf("Client ID: %d\tlines: %d\twords: %d\tcharacters: %d\n", d.job.id, d.lines, d.words, d.characters)
}
finished <- true
}()
var wg sync.WaitGroup
for i := 0; i < nWorkers; i++ {
wg.Add(1)
go worker(&wg)
}
wg.Wait()
close(data)
fmt.Printf("Finished: %v\n", <-finished)
}
Code explanation
Lines 14–17: We replace the
integerfield in theClientstruct with atextfield that contains a sample text string.Lines 19–24: We also add three fields to the
Resultstruct to hold the number of lines, words, and characters in the text.Lines 30–49: In the
workerfunction, we replace the calculation of the square with a call to thewccommand-line tool. We create a newexec.Cmdstruct to represent the command, set itsStdinfield to a newstrings.Readerthat contains the text from theClient, and then call itsOutputmethod to run the command and capture its output. We then parse the output to extract the line count, word count, and character count and create a newResultstruct to send to thedatachannel.Line 53: Finally, in the
createfunction, we replace the integer value in theClientstruct with a sample"Hello World!"text.