1
\$\begingroup\$

This program simply calculates "something" in separate goroutines and the main goroutine finally terminates after all the goroutines are finished.

The question i wanted to ask was, is the way of quitting the infinite for loop in the printChannels() an acceptable one? Or does anyone has a better approach to doing this?

package concurrency

import (
    "goreceipes/concurrency/syncutils"
    "fmt"
    "math"
)

// create a struct to hold the number and it's processed value
type NumberObject struct {
    number int
    value  int
}

type QuitObject struct {
    channelName string
    quitValue int
}

// Main
func main() {
    // notify the main of 4 threads in play
    syncutils.Wg.Add(4)

    // create channels for each function
    squareCh := make(chan NumberObject)
    fibCh := make(chan NumberObject)
    dblCh := make(chan NumberObject)
    quitCh := make(chan QuitObject, 3)

    // launch threads to calculate values
    go calculateSquares(squareCh, quitCh)
    go calculateFibonacci(fibCh, quitCh)
    go calculateDouble(dblCh, quitCh)

    // launch the printer thread
    go printChannels(squareCh, fibCh, dblCh, quitCh)

    // wait for threads to complete
    syncutils.Wg.Wait()

    fmt.Println("Terminating program.")
}

// print the output of each channel
func printChannels(sqCh <-chan NumberObject, fibCh <-chan NumberObject, dblCh <-chan NumberObject, quitCh <- chan QuitObject) {
    // let the 'main' know i'm done
    defer syncutils.Wg.Done()

    // maintains a count of how many channels are exhausted
    channelMap := make(map[string]int)

    // initialize with zero. After each goroutine is done executing, they will send a "1" into the map as a "quit signal"
    channelMap["sqCh"] = 0
    channelMap["fibCh"] = 0
    channelMap["dblCh"] = 0

    for {
        select {
        case obj := <- sqCh:
            fmt.Printf("Square of %d = \t%d\n", obj.number, obj.value)
        case obj := <- fibCh:
            fmt.Printf("Fibonacci of %d = %d\n", obj.number, obj.value)
        case obj := <- dblCh:
            fmt.Printf("Double of %d = \t%d\n", obj.number, obj.value)
        case val := <- quitCh:
            channelMap[val.channelName] = val.quitValue
            if channelMap["sqCh"] == 1 && channelMap["fibCh"] == 1 && channelMap["dblCh"] == 1{
                fmt.Println("All channels are done executing. Break the infinite loop")
                return
            }
        }
    }
}

// calculates double
func calculateDouble(dblCh chan<- NumberObject, quitCh chan <- QuitObject) {
    defer syncutils.Wg.Done()

    for i := 0; i < 10; i++ {
        dblCh <- NumberObject{number: i, value: i * 2}
    }
    // send the quit signal
    quitCh <- QuitObject{"dblCh", 1}
}

// calculate fibonacci
func calculateFibonacci(fibCh chan<- NumberObject, quitCh chan <- QuitObject) {
    // let the main know I'm done
    defer syncutils.Wg.Done()

    for i := 0; i < 10; i++ {
        num := float64(i)
        Phi := (1 + math.Sqrt(num)) / 2
        phi := (1 - math.Sqrt(num)) / 2
        result := (math.Pow(Phi, num) - math.Pow(phi, num)) / math.Sqrt(5)
        fibCh <- NumberObject{number: int(num), value: int(result)}
    }
    // send the quit signal
    quitCh <- QuitObject{"fibCh", 1}
}

// calculates squares
func calculateSquares(sqCh chan<- NumberObject, quitCh chan <- QuitObject) {
    // let the main know I'm done
    defer syncutils.Wg.Done()

    for i := 0; i < 10; i++ {
        sqCh <- NumberObject{number: i, value: i * i}
    }
    // send the quit signal
    quitCh <- QuitObject{"sqCh", 1}
}

and

syncutils.Wg

is defined in a separate package so it can be used whereever required

package syncutils

import "sync"

var Wg sync.WaitGroup

/**
    the purpose of this file/code is to only provide a global variable Wg = WaitGroup and package it
    so it can be included wherever needed.
 */

Can somebody suggest a better way to terminate an infinite loop which is reading multiple channels in a different goroutine?

Thanks in advance for the help.

\$\endgroup\$
1
\$\begingroup\$

You can do without the chan QuitObject:

As you mention, the main issue is to quit the printChannels properly. Since it is listening on 3 channels, it is quite complicated to achieve.

To solve this, I recommend adding a chan string which would (concurrently) receive the string to print from the 3 others channels and close once they are done.

The printChannels becomes printResult (it could actually be directly written in the main):

func printResult(in <-chan string) {
    for s := range in { // this loop will end once the input channel is closed
        fmt.Println(s)
    }
}

To feed this channel, we need goroutines which take a result and create a string:

func formatResult(in <-chan NumberObject, out chan<- string, format string) {
    for n := range in { // this loop will end once the input channel is closed
        out <- fmt.Sprintf(format, n.number, n.value)
    }
}
// usage:
resultCh := make(chan string)
go formatResult(squareCh, resultCh, "Square of %d = \t%d")
go formatResult(fibCh, resultCh, "Fibonacci of %d = %d")
go formatResult(dblCh, resultCh, "Double of %d = \t%d")
// close the resultCh at the end

We actually just moved the issue : we now need to know when to close the resultCh!

The WaitGroup make sense here (a local variable is all that we need):

var wg = sync.WaitGroup{}
wg.Add(3)
go func() {
    formatResult(squareCh, resultCh, "Square of %d = \t%d")
    wg.Done()
}()
go func() {
    formatResult(fibCh, resultCh, "Fibonacci of %d = %d")
    wg.Done()
}()
go func() {
    formatResult(dblCh, resultCh, "Double of %d = \t%d")
    wg.Done()
}()

go func() {
    // wait for threads to complete
    wg.Wait()
    resultCh <- "All channels are done executing."
    close(resultCh)
}()

But as-is, the formatResult never finish, because the input channel is never closed. So we need to modify the computation functions to close the channel after the for loop.

// calculates squares
func calculateSquares(sqCh chan<- NumberObject) {
    for i := 0; i < 10; i++ {
        sqCh <- NumberObject{number: i, value: i * i}
    }
    close(sqCh)
}

As a further refinement, you could change the computation functions to be just func(int)int and add helper functions to call them and fill the expected channel:

func calculate(f func(int) int, ch chan<- NumberObject) {
    for i := 0; i < 10; i++ {
        ch <- NumberObject{number: i, value: f(i)}
    }
    close(ch)
}

Final program (I integrated the wg.Done inside a calculateAndFormat to reduce verbosity, but it reduces flexibility a bit):

package main

import (
    "fmt"
    "math"
    "sync"
)

func main() {
    resultCh := make(chan string)

    var wg = sync.WaitGroup{}
    wg.Add(3)
    // launch threads to calculate values
    go calculateAndFormat(square, resultCh, "Square of %d = \t%d", &wg)
    go calculateAndFormat(fibonacci, resultCh, "Fibonacci of %d = %d", &wg)
    go calculateAndFormat(double, resultCh, "Double of %d = \t%d", &wg)

    go func() {
        wg.Wait()
        resultCh <- "All channels are done executing."
        close(resultCh)
    }()

    for s := range resultCh {
        fmt.Println(s)
    }

    fmt.Println("Terminating program.")
}

func calculateAndFormat(f func(int) int, ch chan<- string, format string, wg *sync.WaitGroup) {
    for i := 0; i < 10; i++ {
        ch <- fmt.Sprintf(format, i, f(i))
    }
    wg.Done()
}

// calculates double
func double(i int) int {
    return i * 2
}

// calculate fibonacci
func fibonacci(i int) int {
    num := float64(i)
    Phi := (1 + math.Sqrt(num)) / 2
    phi := (1 - math.Sqrt(num)) / 2
    result := (math.Pow(Phi, num) - math.Pow(phi, num)) / math.Sqrt(5)
    return int(result)
}

// calculates squares
func square(i int) int {
    return i * i
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.