Go Lesson 32 – Goroutines | Dataplexa

Goroutines in Go (In Depth)

Goroutines are the foundation of concurrency in Go. They allow functions to run independently and concurrently with very low overhead. In this lesson, you will deeply understand how goroutines work, how they are scheduled, and how to use them safely and effectively.


What Is a Goroutine?

A goroutine is a lightweight thread managed by the Go runtime. Unlike operating system threads, goroutines are cheap to create and consume very little memory.

A typical OS thread may require several megabytes of memory, while a goroutine starts with only a few kilobytes and grows as needed.


Why Goroutines Are Lightweight

  • Managed by the Go runtime, not the OS
  • Fast creation and destruction
  • Efficient scheduling
  • Scales to thousands or millions of tasks

This makes Go ideal for servers, APIs, and concurrent data processing.


Starting a Goroutine

You start a goroutine by prefixing a function call with the go keyword.

go someFunction()

Once started, the goroutine runs concurrently with the rest of the program.


Basic Goroutine Example

package main

import (
    "fmt"
    "time"
)

func greet(name string) {
    fmt.Println("Hello,", name)
}

func main() {
    go greet("Alice")
    go greet("Bob")
    time.Sleep(time.Second)
}

Here, two goroutines execute independently. The sleep ensures the main function does not exit early.


Understanding Program Exit

When the main function exits, all goroutines are immediately terminated.

This is why you must explicitly coordinate goroutines instead of relying on timing.


Goroutine Scheduling (Conceptual)

Go uses an internal scheduler based on three components:

  • G – Goroutine
  • M – OS Thread (Machine)
  • P – Processor (logical execution context)

The runtime maps many goroutines onto a small number of OS threads, efficiently multiplexing execution.


Example with Loop and Goroutines

A common beginner mistake is launching goroutines inside loops incorrectly.

package main

import (
    "fmt"
    "time"
)

func main() {
    for i := 1; i <= 3; i++ {
        go func(n int) {
            fmt.Println("Goroutine:", n)
        }(i)
    }

    time.Sleep(time.Second)
}

Here, each goroutine correctly receives its own value of i.


Incorrect Loop Example (Do Not Do This)

for i := 1; i <= 3; i++ {
    go func() {
        fmt.Println(i)
    }()
}

This can cause unexpected results because all goroutines may access the same variable.


Real-World Example: Processing Orders

Imagine processing multiple orders in an e-commerce system. Each order can be handled independently.

func processOrder(orderID int) {
    fmt.Println("Processing order:", orderID)
}

func main() {
    orders := []int{101, 102, 103, 104}

    for _, id := range orders {
        go processOrder(id)
    }

    time.Sleep(time.Second)
}

Each order is processed concurrently, improving performance and responsiveness.


When to Use Goroutines

  • Handling multiple requests
  • Background processing
  • Parallel data processing
  • I/O-bound tasks

When Not to Use Goroutines

  • Very small, sequential tasks
  • When shared state is not synchronized
  • When concurrency adds unnecessary complexity

Key Takeaways

  • Goroutines are lightweight and efficient
  • They run concurrently, not sequentially
  • Main function controls program lifetime
  • Proper synchronization is required

Practice Exercises

Exercise 1

Create a program that launches five goroutines, each printing a unique number.

Exercise 2

Modify the program to process a slice of values concurrently.


What’s Next?

In the next lesson, you will learn about Channels — the core mechanism Go uses to communicate between goroutines safely.