Search⌘ K
AI Features

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)
}
wPools.go

Code explanation

  • Lines 14–17: We replace the integer field in the Client struct with a text field that contains a sample text string.

  • Lines 19–24: We also add three fields to the Result struct to hold the number of lines, words, and characters in the text.

  • Lines 30–49: In the worker function, we replace the calculation of the square with a call to the wc command-line tool. We create a new exec.Cmd struct to represent the command, set its Stdin field to a new strings.Reader that contains the text from the Client, and then call its Output method 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 new Result struct to send to the data channel.

  • Line 53: Finally, in the create function, we replace the integer value in the Client struct with a sample "Hello World!" text.