Skip to main content

Command Palette

Search for a command to run...

Preventing goroutine leaks

Updated
1 min read
Preventing goroutine leaks
T

Just a guy who loves to write code and watch anime.

The done Channel Pattern

func doWork(done <-chan interface{}) <-chan interface{} {
    terminated := make(chan interface{})
    go func() {
        defer close(terminated)
        for {
            select {
            case s := <-strings:
                // Do work
            case <-done:  // Parent says "stop working"
                return
            }
        }
    }()
    return terminated
}

Key insight: Pass a done channel to every goroutine so the parent can signal cancellation.

Preventing Goroutine Leaks

Without cancellation (bad):

newRandStream := func() <-chan int {
    randStream := make(chan int)
    go func() {
        for {
            randStream <- rand.Int()  // runs forever!
        }
    }()
    return randStream
}

With cancellation (good):

newRandStream := func(done <-chan interface{}) <-chan int {
    randStream := make(chan int)
    go func() {
        defer close(randStream)
        for {
            select {
            case randStream <- rand.Int():
            case <-done:
                return  // clean exit
            }
        }
    }()
    return randStream
}

for-select Loop Pattern

This pattern shows up everywhere in Go:

for {
    select {
    case <-done:
        return
    default:
        // do non-preemptable work
    }
}

Two variations:

  • With default: Non-blocking, checks done channel between work

  • Without default: Blocking, waits for channels