Skip to main content
Golang

Print Odd-Even Numbers Synchronously Using Goroutines and Channels in Go

Prerna Sharma

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.

Communication between the two Goroutines using channels
Communication between the two Goroutines using channels

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 {
}
💡
The empty struct type is used because of memory optimization,
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 empty Notify struct on the printEven 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 empty Notify struct on the printOdd 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 the printOdd 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.