Synchronization in Go (But Your Hands Are Tied)

// Figure 1
type Daemon struct {
// .... omitted
}
func (d *Daemon) Run(ctx context.Context) {
for {
select {
case: <-ctx.Done:
return
default:
}
// do your thing
}
}
// Caller's code somewhere
ctx, cancel := context.WithCancel(...)
d.Run(ctx)
// sometime later...
cancel()
// Figure 2
type Daemon struct {
done chan struct{}
exited chane struct{}
}
for (d *Daemon) Run() {
d.done = make(chan struct{})
d.exited = make(chan struct{})
defer close(d.exited)
for {
select {
case <-d.done:
return
default:
}
// do your thing
}
}
func (d *Daemon) Stop() {
close(d.done)
}
func (d *Daemon) Wait() {
<-d.exited
}

Avoiding the Uninitialized

// Figure 3
type daemon struct { ... } // unexported
func New() *daemon {
return &daemon{...}
}

Making Sense of the Chaotic Ordering

// Figure 4
var d Daemon
go d.Stop() // goroutine 1
go d.Run() // goroutine 2
d.Wait() // goroutine 3, or current goroutine

Dancing With The Synchronization

// Figure 5
type Daemon struct {
done chan struct{}
exited chan struct{}
mu sync.RWMutex
}
func (d *Daemon) Run() {
d.mu.Lock()
d.done = make(chan struct{})
d.exited = make(chan struct{})
d.mu.Unlock()
LOOP:
for {
select {
case <-d.done:
fmt.Println("detected stop")
break LOOP
default:
}
// do your thing
}
d.mu.Lock()
close(d.exited) // won't reset d.exited, because it will be used by Wait()
d.done = nil
d.mu.Unlock()
fmt.Println("clean exit")
}
// Figure 6
func (d *Daemon) Stop() {
d.mu.Lock()
for d.done == nil { // has not started
d.mu.Unlock()
<-time.After(100*time.Millisecond)
d.mu.Lock()
}
fmt.Println("Stop!")
close(d.done)
d.mu.Unlock()
}
func (d *Daemon) Wait() {
d.mu.RLock()
for d.exited == nil {
d.mu.RUnlock()
<-time.After(100*time.Millisecond)
d.mu.RLock()
}
ch := d.exited
d.mu.RUnlock()
<-ch
fmt.Println("wait done")
}

Then We Force Our Will Unto The Users

// Figure 7, the "correct" way to use
var d Daemon

go d.Run()
// mimic somebody stopping the daemon
time.AfterFunc(time.Second, d.Stop)
d.Wait()
// Figure 8, the "out-of-order" way to use
var d Daemon
go d.Stop()
go d.Wait()
go d.Run()
// give the goroutines chance to exit
<-time.After(time.Second)
Stop!
detected stop
clean exit
wait done

--

--

Go/perl hacker; author of peco; works @ Mercari; ex-mastermind of builderscon; Proud father of three boys;

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store