Problem Statement
Print a series of odd-even numbers synchronously using the concept of Goroutines and Channels in Go.
Expected Output
Press Enter to Exit
1 2 3 4 5 6 7 8
Finished!
This is one of the very important questions of Golang Interviews. In this article, we will be looking at how we can write a Golang program to achieve the same.
In this blog, we discussed a simpler version of this problem where synchronisation is not a requirement and we just need to print odd even numbers using two Goroutines. If you are ready to move with our current problem, then let's continue.
Intuition
The idea is to have two Goroutine running and print the next number in the series based on the signal received from different channels i.e. printEven
and printOdd
.
Let's try to understand it in detail.
Let's say we have two channels printOdd
and printEven
and we have two Goroutine running, let's say Goroutine A
and Goroutine B
.
For simplicity, let's say Goroutine A
takes care of printing the odd values when a signal is received from printOdd
channel and Goroutine B
takes care of printing the even values when a signal is received from printEven
channel.
As soon as Goroutine A
receives a signal from printOdd
channel, it prints an odd number, increments it by 2, and at the same time notifies the Goroutine B
by sending an arbitrary value to printEven
channel.
On the other hand, Goroutine B
is waiting to receive a signal from printEven
channel and as soon as it receives a signal, it prints an even number, increments it by 2, and at the same time notifies the Goroutine A
by sending any arbitrary value to printOdd
channel.
Arbitrary Value
An empty Notify
struct is defined and we will use it as an arbitrary value. This struct is used as a signal to notify the goroutines when to perform certain actions.
type Notify struct {
}
since we don't want to share any other data, just notify the other Goroutine.
Goroutine A
go func() {
start := 1
for {
select {
case <-printOdd:
time.Sleep(time.Millisecond * 500)
fmt.Printf("%d ", start)
start = start + 2
printEven <- Notify{}
case <-closer:
return
}
}
}()
- Goroutine A runs an infinite loop.
- It waits for a signal on the
printOdd
channel. When received, it prints the current odd number, increments the counter, and signals Goroutine B to print an even number by sending an emptyNotify
struct on theprintEven
channel. - If a signal is received on the
closer
channel, Goroutine A returns, terminating the Goroutine.
Goroutine B
go func() {
start := 2
for {
select {
case <-printEven:
time.Sleep(time.Millisecond * 500)
fmt.Printf("%d ", start)
start = start + 2
printOdd <- Notify{}
case <-closer:
return
}
}
}()
- Goroutine B runs an infinite loop.
- It waits for a signal on the
printEven
channel. When received, it prints the current even number, increments the counter, and signals Goroutine A to print an odd number by sending an emptyNotify
struct on theprintOdd
channel. - If a signal is received on the
closer
channel, Goroutine B returns, terminating the Goroutine.
As you have noticed, before printing every number we are making each Goroutine sleep for 500 ms. This is done to visualize how we are printing odd-even numbers alternatively.
User Input and Execution
reader := bufio.NewReader(os.Stdin)
fmt.Println("Press Enter to cancel")
// Notify Goroutine A by sending Notify{} struct to printOdd Channel
printOdd <- Notify{}
// Wait for console input to quit
_, err := reader.ReadString('\n')
if err != nil {
log.Fatal("Error while reading the console.")
}
fmt.Println("Finished!")
- A reader is created to read from the console.
- The message "Press Enter To Quit" is printed.
- The program triggers the initial signal by sending an empty
Notify
struct on theprintOdd
channel. - The program then waits for the user to press Enter. Once Enter is pressed, it prints "Finished!" and proceeds to close the
closer
channel.
Putting Everything Together
Create a odd-even-synchronisation.go
file.
package main
import (
"bufio"
"fmt"
"log"
"os"
"time"
)
// Notify
// An empty struct
type Notify struct {
}
func PrintOddEvenSynchronously() {
// initialize all channels
printOdd := make(chan Notify)
printEven := make(chan Notify)
closer := make(chan Notify)
// spawn Goroutine A
go func() {
start := 1
// An infinite loop which keeps on checking if it has received a signal
// to print next odd number in the series and notify printEven channel.
for {
select {
case <-printOdd:
time.Sleep(time.Millisecond * 500)
fmt.Printf("%d ", start)
start = start + 2
// notify Goroutine B to print an even number
printEven <- Notify{}
case <-closer:
return
}
}
}()
// spawn Goroutine B
go func() {
start := 2
// An infinite loop which keeps on checking if it has received a signal
// to print next even number in the series and notify printOdd channel.
for {
select {
case <-printEven:
time.Sleep(time.Millisecond * 500)
fmt.Printf("%d ", start)
start = start + 2
// notify Goroutine A to print an odd number
printOdd <- Notify{}
case <-closer:
return
}
}
}()
reader := bufio.NewReader(os.Stdin)
fmt.Println("Press Enter To Quit")
// Notify Goroutine A by sending Notify{} struct to printOdd Channel
printOdd <- Notify{}
// Wait for console input to quit
_, err := reader.ReadString('\n')
if err != nil {
log.Fatal("Error while reading the console.")
}
fmt.Println("Finished!")
close(closer)
}
Let's create a main.go
file and call the above function.
package main
func main() {
PrintOddEvenSynchronously()
}
To test the code, fire go run .
in your terminal.
Output
Press Enter To Quit
1 2 3 4 5 6 7 8 9 10 11
Finished!
Conclusion
In this article, we have looked at how we can print the series of odd-even numbers synchronously using Goroutines and Channels in Go.
Connect with us on Discord in case you are stuck or have any questions.