On Github danielhfrank / nuts
Dan Frank * @danielhfrank * github.com/danielhfrank
We've hit many pitfalls. They've led to many better design decisions, but we haven't recorded them anywhere. Sharing x Discussion = Win for everyone
Exit logic and concurrency
func doStuff(workChan chan *work, exitChan chan struct{}){ for { select{ case msg := <- workChan: // do some work case <- exitChan: break } } // cleanup }
Seems pretty clear what the author intended, but what will actually happen here?
The break will only break out of the select statement!
No shortage of options to fix, I prefer replacing with return
func doStuff(workChan chan *work, exitChan chan struct{}){ defer func(){ //cleanup }() for { select{ case msg := <- workChan: // do some work case <- exitChan: return } } }
What happens when both channels have messages?
Select picks from one of them arbitrarily!
func doStuff(workChan chan *work, exitChan chan struct{}){ defer func(){ //cleanup }() for { select{ case msg := <- workChan: // do some work default: select{ case <- exitChan: return default: } } } }
We can communicate the end of messages on a channel using the close statement, and iterate using the range statement
func doStuff(workChan chan *work){ for msg := range workChan { // do some work } // cleanup }
Close the incoming channel elsewhere when you are ready to quit
func doStuff(workChan chan *work, otherChan *work, exitChan chan struct{}){ defer func(){ //cleanup }() for { select{ case msg := <- workChan: // do some work case otherMsg := <- otherChan: // do some other kind of work case <- exitChan: return } } }
How to correctly handle arbitrary selects when muxing over multiple channels?
Reads lines from stdin and publishes them to an http endpoint bit.ly/file2http
First attempt: ALL THE GOROUTINES
func main(){ reader := bufio.NewReader(os.Stdin) for { line, err := reader.ReadString('\n') if err != nil { break } go publish(line) } }
Better: parellelize requests, but know numPublishers
func publish(msgChan chan []byte) { for msg := range msgChan { // publish it (might take a long time!) } } func main(){ msgChan := make(chan []byte, 5) for i := 0, i < numPublishers, i++ { go publish(msgChan) } for { // read line and handle error msgChan <- line } close(msgChan) }
Program will exit with msgs still in the channel buffer!
Unfortunately, unbuffering doesn't quite solve our problems
The goroutine that picks the value off the channel can and will get interrupted by the main goroutine exiting
Quite simply, worker goroutines need to be able to tell the main goroutine when they are done
OK, take two
func publish(msgChan chan []byte, doneChan chan bool) { for msg := range msgChan { // publish it (might take a long time!) } doneChan <- true // signal completion } func main(){ msgChan := make(chan []byte, 5) doneChan := make(chan bool) for i := 0, i < numPublishers, i++ { go publish(msgChan, doneChan) } for { // read line and handle error msgChan <- line } close(msgChan) // wait for the right number of completion signals for i := 0, i < numPublishers, i++ { <-doneChan } }
My preferred way...
func publish(msgChan chan []byte, doneChan chan bool) { for msg := range msgChan { // publish it (might take a long time!) } doneChan <- true } func main(){ msgChan := make(chan []byte, 5) doneChan := make(chan bool) for i := 0, i < numPublishers, i++ { go publish(msgChan, doneChan) // each time we fire off publish, make sure we wait defer func(){<-doneChan}() } for { // read line and handle error msgChan <- line } close(msgChan) }
Streamline with sync.WaitGroup
func publish(msgChan chan []byte, waitGroup *sync.WaitGroup) { for msg := range msgChan { // publish it (might take a long time!) } waitGroup.Done() } func main(){ msgChan := make(chan []byte, 5) waitGroup := &sync.WaitGroup{} waitGroup.Add(numPublishers) for i := 0, i < numPublishers, i++ { go publish(msgChan, waitGroup) } for { // read line and handle error msgChan <- line } close(msgChan) waitGroup.Wait() }
Now is a good time to politely run for the exits
Or ask questions
Or take issue with anything I've said up to this point
"Object Oriented Programming" in Go
...otherwise known as, "use interfaces"
or, James Gosling's nightmare
The caller of a method doesn't need to know exactly what type of object it is calling, the "right" method will still get called
Within constraints of the language's type system, of course
Going boldly where many have failed before...
Base type: Knicks Fan
Subtype: Newly Converted Nets Fan
Let's look at how they behave in a traditional object oriented language
public class KnicksFan extends Object { public void cheer(){ System.out.println("Go Knicks!"); } } public class NewlyConvertedNetsFan extends KnicksFan { @Override public void cheer(){ System.out.println("Go Nets!"); } } public class MadisonSquareGarden extends Object { public static void makeSomeNoise(KnicksFan fan){ fan.cheer(); } public static void main(String... args) { NewlyConvertedNetsFan conflictedFan = new NewlyConvertedNetsFan(); makeSomeNoise(conflictedFan); // prints "Go Nets!" } }
Works in Python too
class KnicksFan(object): def cheer(self): print "Go Knicks!" class NewlyConvertedNetsFan(KnicksFan): def cheer(self): print "Go Nets!" if __name__ == '__main__': fan = NewlyConvertedNetsFan() print isinstance(fan, KnicksFan) # True fan.cheer() # Go Nets!
Now let's try the same thing in Go!
We've heard that embedding is just like inheritance but better, so we'll use that
type KnicksFan struct{} func (k *KnicksFan) Cheer(){ log.Println("Go Knicks") } type NewlyConvertedNetsFan struct{ KnicksFan } func (n *NewlyConvertedNetsFan) Cheer(){ log.Println("Go Nets") } func MakeSomeNoise(f *KnicksFan){ f.Cheer() } func main(){ conflictedFan := &NewlyConvertedNetsFan{} MakeSomeNoise(conflictedFan) }
Doesn't compile!
-Rob Pike
Screw that, we just want it to compile!
type KnicksFan struct{} func (k *KnicksFan) Cheer(){ log.Println("Go Knicks") } type NewlyConvertedNetsFan struct{ KnicksFan } func (n *NewlyConvertedNetsFan) Cheer(){ log.Println("Go Nets") } func MakeSomeNoise(f *KnicksFan){ f.Cheer() } func main(){ conflictedFan := &NewlyConvertedNetsFan{} MakeSomeNoise(&conflictedFan.KnicksFan) // <== }
Compiles, but it prints "Go Knicks"
The problem is, we're using the wrong metaphor
How can we acheive polymorphism-like behavior?
Use interfaces
Specifically, the interface defines exactly which methods can have polymorphic behavior
Fixed!
type Fan interface{ Cheer() } type KnicksFan struct{} func (k *KnicksFan) Cheer(){ log.Println("Go Knicks") } type NewlyConvertedNetsFan struct{ KnicksFan } func (n *NewlyConvertedNetsFan) Cheer(){ log.Println("Go Nets") } func MakeSomeNoise(f Fan){ // <== f.Cheer() } func main(){ conflictedFan := &NewlyConvertedNetsFan{} MakeSomeNoise(conflictedFan) }
These slides: bit.ly/go_nuts
Made with reveal.js
Dan Frank * @danielhfrank * github.com/danielhfrank