1. go
  2. /concurrency

Understanding Concurrency in Go Programming

Go's approach to concurrency is one of its most distinctive and powerful features. This guide covers everything you need to know about concurrent programming in Go.

Core Concepts

  1. Goroutines

    • Lightweight threads
    • Concurrent execution
    • Scheduling
    • Best practices
  2. Channels

    • Communication
    • Synchronization
    • Buffered channels
    • Channel patterns
  3. Select

    • Multiple channel operations
    • Timeouts
    • Default cases
    • Common patterns
  4. Mutexes

    • Synchronization
    • Lock types
    • Best practices
    • Deadlock prevention
  5. Context

    • Cancellation
    • Timeouts
    • Value propagation
    • Best practices

Advanced Topics

Common Patterns

1. Basic Goroutine

// Launch concurrent operation
go func() {
    // Do work
    fmt.Println("Working...")
}()

2. Channel Communication

// Create channel
ch := make(chan string)

// Send data
go func() {
    ch <- "Hello"
}()

// Receive data
msg := <-ch

3. Worker Pool

func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        results <- j * 2
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)

    // Start workers
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

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

    // Collect results
    for a := 1; a <= 9; a++ {
        <-results
    }
}

Best Practices

1. Goroutine Management

  • Always know how your goroutines will end
  • Use WaitGroups for synchronization
  • Handle panics in goroutines
  • Consider using worker pools
var wg sync.WaitGroup
wg.Add(1)
go func() {
    defer wg.Done()
    // Do work
}()
wg.Wait()

2. Channel Usage

  • Use buffered channels when appropriate
  • Always close channels from the sender side
  • Handle closed channels gracefully
  • Use directional channel parameters
// Sender
func produce(ch chan<- int) {
    defer close(ch)
    for i := 0; i < 5; i++ {
        ch <- i
    }
}

// Receiver
func consume(ch <-chan int) {
    for v := range ch {
        fmt.Println(v)
    }
}

3. Error Handling

func doWork() error {
    errCh := make(chan error, 1)
    go func() {
        if err := riskyOperation(); err != nil {
            errCh <- err
            return
        }
        errCh <- nil
    }()
    
    select {
    case err := <-errCh:
        return err
    case <-time.After(time.Second):
        return errors.New("timeout")
    }
}

Common Concurrency Patterns

1. Pipeline Pattern

func gen(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for _, n := range nums {
            out <- n
        }
    }()
    return out
}

func sq(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for n := range in {
            out <- n * n
        }
    }()
    return out
}

2. Fan-out, Fan-in

func fanOut(ch <-chan int, n int) []<-chan int {
    outputs := make([]<-chan int, n)
    for i := 0; i < n; i++ {
        outputs[i] = sq(ch)
    }
    return outputs
}

func fanIn(channels ...<-chan int) <-chan int {
    var wg sync.WaitGroup
    merged := make(chan int)
    
    output := func(c <-chan int) {
        defer wg.Done()
        for n := range c {
            merged <- n
        }
    }
    
    wg.Add(len(channels))
    for _, c := range channels {
        go output(c)
    }
    
    go func() {
        wg.Wait()
        close(merged)
    }()
    
    return merged
}

Performance Considerations

  1. Goroutine Overhead

    • Goroutines are lightweight but not free
    • Consider pooling for very high numbers
    • Monitor goroutine count
  2. Channel Operations

    • Buffered vs unbuffered trade-offs
    • Channel closure costs
    • Select performance
  3. Synchronization Costs

    • Mutex vs channel trade-offs
    • Lock contention
    • Memory synchronization

Debugging Concurrent Programs

  1. Race Detector

    go run -race main.go
    go test -race ./...
    
  2. Deadlock Detection

    • Go runtime automatically detects deadlocks
    • Use timeout patterns for prevention
  3. Profiling

    import _ "net/http/pprof"
    

Next Steps

  1. Study standard library concurrency primitives
  2. Learn about testing concurrent code
  3. Explore best practices for production code
  4. Practice with real-world examples

Additional Resources