blob: 727a121aa9f20a63720045130480ba11c5058cc6 [file] [log] [blame]
Go: code that grows with grace
Andrew Gerrand
Google Sydney
* Video
A video of this talk was recorded at Øredev in Malmö, Sweden in November 2012.
.link Watch the talk on Vimeo
* Go
You may have heard of Go.
It's my favorite language. I think you'll like it, too.
* What is Go?
An open source (BSD licensed) project:
- Language specification,
- Small runtime (garbage collector, scheduler, etc),
- Two compilers (`gc` and `gccgo`),
- 'Batteries included' standard library,
- Tools (build, fetch, test, document, profile, format),
- Documentation.
As of September 2012 we have more than 300 contributors.
* Go is about composition
Go is Object Oriented, but not in the usual way.
- no classes (methods may be declared on any type)
- no subtype inheritance
- interfaces are satisfied implicitly (structural typing)
The result: simple pieces connected by small interfaces.
* Go is about concurrency
Go provides CSP-like concurrency primitives.
- lightweight threads (goroutines)
- typed thread-safe communication and synchronization (channels)
The result: comprehensible concurrent code.
* Go is about gophers
.image chat/gophers.jpg
* Core values
Go is about composition, concurrency, and gophers.
Keep that in mind.
* Hello, go
.play chat/support/hello.go
* Hello, net
.play chat/support/hello-net.go
* Interfaces
Hey neato! We just used `Fprintln` to write to a net connection.
That's because a `Fprintln` writes to an `io.Writer`, and `net.Conn` is an `io.Writer`.
.code chat/support/hello-net.go /Fprintln/
.code chat/support/defs.go /Fprintln/
.code chat/support/defs.go /type.Writer/,/^}/
.code chat/support/defs.go /type.Conn/,/^}/
* An echo server
.play chat/support/echo-no-concurrency.go
* A closer look at io.Copy
.code chat/support/echo-no-concurrency.go /Copy/
.code chat/support/defs.go /Copy/,/func/
.code chat/support/defs.go /type.Conn/,/^}/
.code chat/support/defs.go /type.Writer/,/^}/
.code chat/support/defs.go /type.Reader/,/^}/
* Goroutines
Goroutines are lightweight threads that are managed by the Go runtime. To run a function in a new goroutine, just put `"go"` before the function call.
.play chat/support/goroutines.go
* A concurrent echo server
.play chat/support/echo.go
* "Chat roulette"
In this talk we'll look at a simple program, based on the popular "chat roulette" site.
In short:
- a user connects,
- another user connects,
- everything one user types is sent to the other.
* Design
The chat program is similar to the echo program. With echo, we copy a connection's incoming data back to the same connection.
For chat, we must copy the incoming data from one user's connection to another's.
Copying the data is easy. As in real life, the hard part is matching one partner with another.
* Design diagram
.image chat/diagrams.png
* Channels
Goroutines communicate via channels. A channel is a typed conduit that may be synchronous (unbuffered) or asynchronous (buffered).
.play chat/support/chan.go
* Select
A select statement is like a switch, but it selects over channel operations (and chooses exactly one of them).
.play chat/support/select.go
* Modifying echo to create chat
In the accept loop, we replace the call to `io.Copy`:
.code chat/support/echo.go /for {/,/\n }/
with a call to a new function, `match`:
.code chat/tcp-simple/chat.go /for {/,/\n }/
* The matcher
The `match` function simultaneously tries to send and receive a connection on a channel.
- If the send succeeds, the connection has been handed off to another goroutine, so the function exits and the goroutine shuts down.
- If the receive succeeds, a connection has been received from another goroutine. The current goroutine then has two connections, so it starts a chat session between them.
.code chat/tcp-simple/chat.go /var.partner/,/^}/
* The conversation
The chat function sends a greeting to each connection and then copies data from one to the other, and vice versa.
Notice that it launches another goroutine so that the copy operations may happen concurrently.
.code chat/tcp-simple/chat.go /,/^}/
* Demo
* Error handling
It's important to clean up when the conversation is over. To do this we send the error value from each `io.Copy` call to a channel, log any non-nil errors, and close both connections.
.code chat/tcp/chat.go /,/^}/
.code chat/tcp/chat.go /func.cp/,/^}/
* Demo
* Taking it to the web
"Cute program," you say, "But who wants to chat over a raw TCP connection?"
Good point. Let's modernize it by turning it a web application.
Instead of TCP sockets, we'll use websockets.
We'll serve the user interface with Go's standard `net/http` package, and websocket support is provided by the `websocket` package from the `` sub-repository,
* Hello, web
.play chat/support/hello-web.go
* Hello, WebSocket
.code chat/support/websocket.js
.play chat/support/websocket.go
* Using the http and websocket packages
.code chat/http/chat.go /package/,/^}/
* Serving the HTML and JavaScript
.code chat/http/html.go /import/
.code chat/http/html.go /func/,/<script>/
.code chat/http/html.go /websocket.=/,/onClose/
.code chat/http/html.go /<\/html>/,$
* Adding a socket type
We can't just use a `websocket.Conn` instead of the `net.Conn`, because a `websocket.Conn` is held open by its handler function. Here we use a channel to keep the handler running until the socket's `Close` method is called.
.code chat/http-noembed/chat.go /type.socket/,/END/
* Struct embedding
Go supports a kind of "mix-in" functionality with a feature known as "struct embedding". The embedding struct delegates calls to the embedded type's methods.
.play chat/support/embed.go /type/,$
* Embedding the websocket connection
By embedding the `*websocket.Conn` as an `io.ReadWriter`, we can drop the explicit `socket` `Read` and `Write` methods.
.code chat/http/chat.go /type.socket/,/END/
* Demo
* Relieving loneliness
What if you connect, but there's noone there?
Wouldn't it be nice if we could synthesize a chat partner?
Let's do it.
* Generating text with markov chains
.code chat/support/markov.txt
* Generating text with markov chains
Fortunately, the Go docs include a markov chain implementation:
We'll use a version that has been modified to be safe for concurrent use.
.code chat/markov/markov.go /Chain/,/{/
.code chat/markov/markov.go /Write/,/{/
.code chat/markov/markov.go /Generate/,/{/
* Feeding the chain
We will use all text that enters the system to build the markov chains.
To do this we split the socket's `ReadWriter` into a `Reader` and a `Writer`,
and feed all incoming data to the `Chain` instance.
.code chat/markov/chat.go /type.socket/,/^}/
.code chat/markov/chat.go /var.chain/,/^}/
* The markov bot
.code chat/markov/chat.go /\/\/.Bot/,/^}/
.code chat/markov/chat.go /,/^}/
.code chat/markov/chat.go /func.+bot.+Write/,/^}/
.code chat/markov/chat.go /func.+bot.+speak/,/^}/
* Integrating the markov bot
The bot should jump in if a real partner doesn't join.
To do this, we add a case to the select that triggers after 5 seconds, starting a chat between the user's socket and a bot.
.code chat/markov/chat.go /func.match/,/^}/
The `chat` function remains untouched.
* Demo
* One more thing
* TCP and HTTP at the same time
.code chat/both/chat.go /func main/,/^}/
.code chat/both/chat.go /func netListen/,/^}/
* Demo
* Discussion
* Further reading
All about Go:
The slides for this talk:
"Go Concurrency Patterns" by Rob Pike: