Skip to main content
โšก Calmops

Goroutines: Lightweight Concurrency

Goroutines: Lightweight Concurrency

Goroutines are Go’s lightweight concurrency mechanism. They enable efficient concurrent programming with minimal overhead.

Creating Goroutines

Basic Goroutine

package main

import (
    "fmt"
    "time"
)

func printNumbers() {
    for i := 1; i <= 5; i++ {
        fmt.Println(i)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    // Start goroutine
    go printNumbers()

    // Main continues
    fmt.Println("Main finished")
    time.Sleep(1 * time.Second)
}

Multiple Goroutines

package main

import (
    "fmt"
    "time"
)

func worker(id int) {
    for i := 0; i < 3; i++ {
        fmt.Printf("Worker %d: %d\n", id, i)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    // Start multiple goroutines
    for i := 1; i <= 3; i++ {
        go worker(i)
    }

    time.Sleep(1 * time.Second)
}

Goroutine Synchronization

Using WaitGroup

package main

import (
    "fmt"
    "sync"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Printf("Worker %d started\n", id)
    fmt.Printf("Worker %d finished\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }

    wg.Wait()
    fmt.Println("All workers finished")
}

Using Channels for Synchronization

package main

import (
    "fmt"
)

func worker(id int, done chan bool) {
    fmt.Printf("Worker %d started\n", id)
    fmt.Printf("Worker %d finished\n", id)
    done <- true
}

func main() {
    done := make(chan bool, 3)

    for i := 1; i <= 3; i++ {
        go worker(i, done)
    }

    // Wait for all workers
    for i := 0; i < 3; i++ {
        <-done
    }

    fmt.Println("All workers finished")
}

Goroutine Patterns

Fan-Out Pattern

package main

import (
    "fmt"
    "sync"
)

func process(id int, results chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    results <- id * 2
}

func main() {
    results := make(chan int, 5)
    var wg sync.WaitGroup

    // Fan-out: start multiple goroutines
    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go process(i, results, &wg)
    }

    // Fan-in: collect results
    go func() {
        wg.Wait()
        close(results)
    }()

    for result := range results {
        fmt.Println(result)
    }
}

Worker Pool Pattern

package main

import (
    "fmt"
    "sync"
)

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        fmt.Printf("Worker %d processing job %d\n", id, job)
        results <- job * 2
    }
}

func main() {
    jobs := make(chan int, 10)
    results := make(chan int, 10)
    var wg sync.WaitGroup

    // Start workers
    numWorkers := 3
    for i := 1; i <= numWorkers; i++ {
        wg.Add(1)
        go worker(i, jobs, results, &wg)
    }

    // Send jobs
    for j := 1; j <= 10; j++ {
        jobs <- j
    }
    close(jobs)

    // Wait for workers
    wg.Wait()
    close(results)

    // Collect results
    for result := range results {
        fmt.Println(result)
    }
}

Goroutine Lifecycle

Goroutine Cleanup

package main

import (
    "fmt"
    "time"
)

func worker(done <-chan bool) {
    for {
        select {
        case <-done:
            fmt.Println("Worker stopped")
            return
        default:
            fmt.Println("Working...")
            time.Sleep(100 * time.Millisecond)
        }
    }
}

func main() {
    done := make(chan bool)

    go worker(done)

    time.Sleep(500 * time.Millisecond)
    done <- true

    time.Sleep(100 * time.Millisecond)
}

Best Practices

โœ… Good: Always Synchronize

// DO: Always wait for goroutines to finish
var wg sync.WaitGroup
wg.Add(1)
go func() {
    defer wg.Done()
    // work
}()
wg.Wait()

โŒ Bad: Fire and Forget

// DON'T: Start goroutines without synchronization
go someFunction()
// Program might exit before goroutine completes

โœ… Good: Use Channels for Communication

// DO: Use channels for goroutine communication
results := make(chan int)
go func() {
    results <- 42
}()
value := <-results

โœ… Good: Limit Goroutine Count

// DO: Use worker pools to limit goroutines
numWorkers := 10
for i := 0; i < numWorkers; i++ {
    go worker(jobs, results)
}

Summary

Goroutines provide:

  1. Lightweight concurrency with minimal overhead
  2. Easy synchronization with channels and WaitGroup
  3. Efficient resource usage compared to threads
  4. Simple concurrent patterns for common tasks
  5. Scalable concurrency for high-performance applications

These features make Go ideal for concurrent programming.

Comments