Go Lesson 48 – Goroutines Project | Dataplexa

Goroutines Project

In this lesson, you will build a real-world mini project using goroutines. This project demonstrates how concurrency improves performance and how goroutines are used in production Go applications.

Instead of learning theory alone, you will design, code, and analyze a concurrent system step by step.


Project Overview

We will build a Concurrent Task Processor that:

  • Processes multiple tasks in parallel
  • Uses goroutines for concurrency
  • Uses channels to communicate results
  • Simulates real processing time

Real-World Scenario

Imagine you have a backend service that needs to process multiple user requests at the same time:

  • Calculating order totals
  • Sending notifications
  • Fetching data from APIs

Using goroutines allows these tasks to run concurrently instead of sequentially.


Project Structure

Create a new Go file:

goroutines_project.go

Step 1: Import Required Packages

package main

import (
    "fmt"
    "time"
)

Step 2: Create a Task Function

This function simulates processing a task. Each task takes a different amount of time.

func processTask(taskID int, results chan<- string) {
    time.Sleep(time.Duration(taskID) * time.Second)
    results <- fmt.Sprintf("Task %d completed", taskID)
}

Each task:

  • Runs independently
  • Sleeps to simulate work
  • Sends its result through a channel

Step 3: Launch Goroutines

Now we launch multiple goroutines to process tasks concurrently.

func main() {
    results := make(chan string)

    for i := 1; i <= 5; i++ {
        go processTask(i, results)
    }

    for i := 1; i <= 5; i++ {
        fmt.Println(<-results)
    }
}

How This Works

Let’s break it down:

  • Five goroutines start almost instantly
  • Each goroutine processes a task independently
  • Results are collected through a channel

The total execution time equals the longest task, not the sum of all tasks.


Sequential vs Concurrent Execution

If this code ran sequentially, total time would be:

  • 1 + 2 + 3 + 4 + 5 = 15 seconds

With goroutines, total time is approximately:

  • 5 seconds

This is the power of concurrency.


Improving the Project with Buffered Channels

Buffered channels can prevent blocking.

results := make(chan string, 5)

This allows goroutines to send results without waiting.


Adding Logging for Better Visibility

func processTask(taskID int, results chan<- string) {
    fmt.Println("Starting task", taskID)
    time.Sleep(time.Duration(taskID) * time.Second)
    fmt.Println("Finishing task", taskID)
    results <- fmt.Sprintf("Task %d completed", taskID)
}

Common Mistakes to Avoid

  • Forgetting to receive from channels
  • Creating goroutines without synchronization
  • Using unbuffered channels incorrectly
  • Launching too many goroutines without limits

Practice Exercises

Exercise 1

Modify the project to process 10 tasks instead of 5.

Exercise 2

Add task IDs and execution timestamps to the output.

Exercise 3

Convert this project to use a worker pool pattern.


Key Takeaways

  • Goroutines allow true concurrent execution
  • Channels safely share data between goroutines
  • Concurrency improves performance dramatically
  • Real projects benefit most from goroutines

What’s Next?

In the next lesson, you will build a complete web application project in Go using everything learned so far.