diff --git a/concurrency/goroutine_leak/main.go b/concurrency/goroutine_leak/main.go index c262739..ad2e5c4 100644 --- a/concurrency/goroutine_leak/main.go +++ b/concurrency/goroutine_leak/main.go @@ -1,6 +1,11 @@ package main -import "fmt" +import ( + "fmt" + "time" + + "github.com/davecgh/go-spew/spew" +) // The goroutine has a few paths to termination: // • When it has completed its work. @@ -15,7 +20,7 @@ goroutines in some sort of organized fashion. **/ func main() { - + cancellationSignal() } // Here we see that the main goroutine passes a nil channel into doWork. Therefore, the @@ -44,6 +49,46 @@ func resourceLeak() { fmt.Println("Done.") } +// The way to successfully mitigate this is to establish a signal between the parent gorou‐ +// tine and its children that allows the parent to signal cancellation to its children. By +// convention, this signal is usually a read-only channel named done. The parent gorou‐ +// tine passes this channel to the child goroutine and then closes the channel when it +// wants to cancel the child goroutine. Here’s an example: func cancellationSignal() { - + // Here we pass the done channel to the doWork function. As a convention, this channel is the first parameter. + doWork := func( + done <-chan interface{}, + strings <-chan string, + ) <-chan interface{} { + terminated := make(chan interface{}) + go func() { + defer fmt.Println("doWork exited.") + defer close(terminated) + for { + select { + case s := <-strings: + fmt.Println(s) + // On this line we see the ubiquitous for-select pattern in use. One of our case statements + // is checking whether our done channel has been signaled. If it has, we return from the goroutine. + case t := <-done: + spew.Dump(t) + return + } + } + }() + return terminated + } + done := make(chan interface{}) + terminated := doWork(done, nil) + // Here we create another goroutine that will cancel the goroutine spawned in + // doWork if more than one second passes. + go func() { + // Cancel the operation after 1 second. + time.Sleep(1 * time.Second) + fmt.Println("Canceling doWork goroutine...") + close(done) + }() + // This is where we join the goroutine spawned from doWork with the main goroutine. + <-terminated + fmt.Println("Done.") }