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.

asked Jul 23, 2017 at 9:14
\$\endgroup\$

1 Answer 1

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
}
answered Jul 24, 2017 at 14:01
\$\endgroup\$
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.