Go

Lesson 07

Goroutines and channel ownership

Make goroutine lifetime and channel closing ownership explicit so concurrent code can stop cleanly.

Good Code

reviews/worker.go
func StartWorker(ctx context.Context, jobs <-chan Job, results chan<- Result) {
    go func() {
        // The sending goroutine owns closing the output channel.
        defer close(results)

        for {
            select {
            case <-ctx.Done():
                // Context cancellation gives the goroutine a clean stop.
                return
            case job, ok := <-jobs:
                if !ok {
                    return
                }
                results <- process(job)
            }
        }
    }()
}

Bad Code

reviews/worker.go
func StartWorker(jobs chan Job, results chan Result) {
    go func() {
        // No cancel path means this goroutine depends on jobs closing.
        for job := range jobs {
            results <- process(job)
        }
    }()
}

func StopWorker(results chan Result) {
    // Closing a channel from the outside can race with senders.
    close(results)
}

Review Notes

What to review

Good Code

The good version shows who reads, who writes, who closes the output channel, and how the goroutine exits when the context is canceled.

Bad Code

The bad version leaves lifetime implicit and lets a separate function close a channel it does not own.

Takeaways

  • The code that starts a goroutine should make its stop condition and channel ownership visible.