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.
1 Answer 1
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
}