Created
October 2, 2016 17:05
-
-
Save joshlf/c3e3a4f45f531bcbb7648d3d3bc3c361 to your computer and use it in GitHub Desktop.
A demonstration of a self-cleaning worker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is an example of a self-cleaning worker. A common pattern in Go | |
// is to have a type which, when created with NewX, spawns one or more | |
// goroutines in the background to perform work. It is usually the | |
// caller's responsibility to call Close() or Stop() in order to shut | |
// these workers down and not leak goroutines and the memory resources | |
// that the goroutines have references to. | |
// | |
// The idea behind a self-cleaning worker is to leverage the runtime's | |
// ability to set finalizers on objects in order to detect when an object | |
// with a still-live worker goroutine has gone out of scope. The type | |
// is split into two types - a *doerHelper, which does the heavy lifting, | |
// and which the worker goroutines have a reference to, and a *doer, which | |
// is the interface from the package consumer's perspective. A *doer | |
// contains a *doerHelper, and methods called on the *doer are simply | |
// wrappers around the same methods on the *doerHelper. The reason for this | |
// setup is that none of the woker goroutines have references to the *doer, | |
// so when the package consumer's copy goes out of scope, the entire object | |
// can be garbage collected. A finalizer is set on the *doer which, when | |
// executed, can clean up the worker goroutines if they haven't been clenaed | |
// up already (the example implementation is simplistic and assumes that | |
// they haven't been cleaned up because it's just for demonstration purposes). | |
// | |
// This program implements an example of this pattern which provides two | |
// creation functions: newDoer, and newSelfCleaningDoer. The former does | |
// the normal thing, and the latter implements the self-cleaning pattern. | |
// First, a number of doers are created with newDoer. Second, a number of | |
// doers are created with newSelfCleaningDoer. The number of goroutines is | |
// continually printed. Finally, the GC is invoked in order to trigger the | |
// finalizers, and the main goroutine sleeps in order to give the worker | |
// goroutines a chance to clean themselves up. In a normal long-lived | |
// program, this would happen more slowly over time. Forcing the GC and | |
// then sleeping is just for demonstration purposes - otherwise, the program | |
// would exit before any of this had a chance to happen. | |
package main | |
import ( | |
"fmt" | |
"runtime" | |
"sync" | |
"time" | |
) | |
func main() { | |
const rounds = 1024 * 64 | |
fmt.Println("Phase 1") | |
for i := 0; i < rounds; i++ { | |
newDoer() | |
if i%1024 == 0 { | |
fmt.Println(runtime.NumGoroutine()) | |
} | |
} | |
fmt.Printf("Goroutines after phase 1: %v\n\n", runtime.NumGoroutine()) | |
fmt.Println("Phase 2") | |
for i := 0; i < rounds; i++ { | |
newSelfCleaningDoer() | |
if i%1024 == 0 { | |
fmt.Println(runtime.NumGoroutine()) | |
} | |
} | |
fmt.Printf("Goroutines after phase 2: %v\n\n", runtime.NumGoroutine()) | |
fmt.Println("Forcing GC...") | |
fmt.Println("Sleeping to give daemons a chance to exit...") | |
runtime.GC() | |
time.Sleep(time.Second) | |
fmt.Printf("Final goroutines: %v\n", runtime.NumGoroutine()) | |
} | |
type doer struct { | |
dh *doerHelper | |
} | |
func newDoer() *doer { | |
d := &doer{&doerHelper{}} | |
d.dh.wg.Add(1) | |
go d.dh.daemon() | |
return d | |
} | |
func newSelfCleaningDoer() *doer { | |
d := &doer{&doerHelper{}} | |
f := func(d *doer) { | |
d.dh.Stop() | |
} | |
runtime.SetFinalizer(d, f) | |
d.dh.wg.Add(1) | |
go d.dh.daemon() | |
return d | |
} | |
type doerHelper struct { | |
wg sync.WaitGroup | |
} | |
func (d *doerHelper) daemon() { | |
d.wg.Wait() | |
} | |
func (d *doerHelper) Stop() { | |
d.wg.Done() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment