Power of Goroutines: From 115 Days to 23 Seconds

2 0
Read Time:3 Minute, 16 Second

Concurrency has become a cornerstone of modern programming, allowing developers to handle multiple tasks simultaneously without the need for complex threading models. One of the standout features of the Go programming language (often referred to as Golang) is its built-in support for concurrency through goroutines. In this blog, we’ll explore a fascinating example that illustrates how goroutines can dramatically reduce the time taken to complete a large number of tasks.

The Challenge: 1 Million Function Calls

Let’s consider a simple function worker, which simulates a long-running task by sleeping for 10 seconds. Here’s the code for our worker function:

func worker(id int, ch chan string) {
	sc := time.Second
	time.Sleep(sc * 10)
	ch <- fmt.Sprintf("Worker %d done after %d seconds", id, 10)
}

In our main function, we spawn 1,000,000 of these worker goroutines:

for i := 1; i <= 1000000; i++ {
    go worker(i, ch)
}

115 days

Each worker sleeps for 10 seconds before sending a completion message back through a channel. If we were to execute these tasks sequentially, we could expect a total execution time of approximately 10 million seconds—nearly 115 days! However, by using goroutines, we harness the power of concurrency.

The Result: Speeding Up Execution

When we run the above code, we find that all 1 million calls complete in approximately 23 seconds.

23 seconds

How is this possible?

Understanding Goroutines

Goroutines are lightweight threads managed by the Go runtime. Unlike traditional threads, which can consume substantial system resources, goroutines are much more efficient. This efficiency stems from several factors:

  1. Low Overhead: Goroutines require very little memory (a few kilobytes) and can be created in large numbers without overwhelming the system.
  2. Multiplexing: The Go runtime can schedule goroutines onto available operating system threads, efficiently utilizing CPU resources. This means that even if we spawn 1 million goroutines, they can run concurrently, overlapping their execution times.
  3. Communication via Channels: Go’s channel mechanism allows goroutines to communicate safely and easily, eliminating the need for locks and reducing the risk of race conditions.

How It Works in Our Example

In our specific example, we launched 1 million worker goroutines that each take 10 seconds to complete. However, since these workers run concurrently, the total time taken is dictated by how many workers can run at once. With multiple CPU cores available, the Go runtime schedules these goroutines effectively:

  • While one set of workers is sleeping, others are actively running.
  • Once the first batch of workers wakes up, the next batch can start working simultaneously, leading to a rapid overall completion time.

The Performance Metrics

To quantify the performance gains:

  • Sequential Execution: 1 million tasks x 10 seconds = 10 million seconds (over 115 days).
  • Concurrent Execution: All tasks complete in approximately 23 seconds.

This stark contrast highlights the efficiency of using goroutines for I/O-bound or long-running tasks where waiting time can overlap.

Source Code

package main

import (
	"fmt"
	"time"
)

func worker(id int, ch chan string) {
	sc := time.Second
	time.Sleep(sc * 10)
	ch <- fmt.Sprintf("Worker %d done  Second :: %d", id, sc)
}

func main() {
	fmt.Println("GO Routines PROGRAM START ************************")
	now := time.Now()
	formatted_time := now.Format("2006-01-02 15:04:05")
	fmt.Println("Current time:", formatted_time)

	ch := make(chan string)

	for i := 1; i <= 1000000; i++ {
		go worker(i, ch)
	}

	for i := 1; i <= 1000000; i++ {
		fmt.Printf(<-ch)
	}
	fmt.Println("GO Routines  FINISH ************************")
	end := time.Now()
	end_time := end.Format("2006-01-02 15:04:05")
	fmt.Println("Current time:", end_time)
}

Conclusion

The results of our experiment showcase the tremendous power of goroutines in Go. By leveraging concurrency, we can transform a task that would normally take an impractical amount of time into one that completes in just seconds. This efficiency not only improves performance but also allows developers to create responsive applications that can handle high levels of concurrency with ease.

Happy
Happy
100 %
Sad
Sad
0 %
Excited
Excited
0 %
Sleepy
Sleepy
0 %
Angry
Angry
0 %
Surprise
Surprise
0 %

About Author

Average Rating

5 Star
0%
4 Star
0%
3 Star
0%
2 Star
0%
1 Star
0%

Leave a Reply

Your email address will not be published. Required fields are marked *