Go for Java Programmers
Sameer Ajmani
Tech Lead Manager, Go team
Sameer Ajmani
Tech Lead Manager, Go team
This talk was presented at NYJavaSIG on April 23, 2015.
2
1. What is Go, and who uses it?
2. Comparing Go and Java
3. Examples
4. Concurrency
5. Tools
"Go is an open source programming language that makes it easy to build simple, reliable, and efficient software."
4Design began in late 2007.
Open source since 2009 with a very active community.
Language stable as of Go 1, early 2012.
5Go is an answer to problems of scale at Google.
Solution: great support for concurrency
In 2011:
Solution: design the language for large code bases
8Lots of projects. Thousands of Go programmers. Millions of lines of Go code.
Public examples:
Lots of projects. Thousands of Go programmers. Millions of lines of Go code.
Public examples:
The target is networked servers, but it's a great general-purpose language.
10Apcera, Bitbucket, bitly, Canonical, CloudFlare, Core OS, Digital Ocean, Docker, Dropbox, Facebook, Getty Images, GitHub, Heroku, Iron.io, Kubernetes, Medium, MongoDB services, Mozilla services, New York Times, pool.ntp.org, Secret, SmugMug, SoundCloud, Stripe, Square, Thomson Reuters, Tumblr, ...
instanceof
)final
Clarity is critical.
When reading code, it should be clear what the program will do.
When writing code, it should be clear how to make the program do what you want.
Sometimes this means writing out a loop instead of invoking an obscure function.
(Don't DRY out.)
For more background on design:
Main.java
public class Main { public static void main(String[] args) { System.out.println("Hello, world!"); } }
hello.go
package main import "fmt" func main() { fmt.Println("Hello, 世界!") }
package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/hello", handleHello) fmt.Println("serving on http://localhost:7777/hello") log.Fatal(http.ListenAndServe("localhost:7777", nil)) } func handleHello(w http.ResponseWriter, req *http.Request) { log.Println("serving", req.URL) fmt.Fprintln(w, "Hello, 世界!") }
Types follow names in declarations.
Exported names are Capitalized. Unexported names are not.
// +build ignore,OMIT
// The server program issues Google search requests. It serves on port 8080.
//
// The /search endpoint accepts these query params:
// q=the Google search query
//
// For example, http://localhost:8080/search?q=golang serves the first
// few Google search results for "golang".
package main
import (
"encoding/json"
"fmt"
"html/template"
"log"
"net/http"
"net/url"
"time"
)
func main() { http.HandleFunc("/search", handleSearch) fmt.Println("serving on http://localhost:8080/search") log.Fatal(http.ListenAndServe("localhost:8080", nil)) } // handleSearch handles URLs like "/search?q=golang" by running a // Google search for "golang" and writing the results as HTML to w. func handleSearch(w http.ResponseWriter, req *http.Request) {
log.Println("serving", req.URL)
// Check the search query.
query := req.FormValue("q") // HL
if query == "" {
http.Error(w, `missing "q" URL parameter`, http.StatusBadRequest)
return
}
// ENDQUERY OMIT
// Run the Google search.
start := time.Now()
results, err := Search(query) // HL
elapsed := time.Since(start)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// ENDSEARCH OMIT
// Render the results.
type templateData struct {
Results []Result
Elapsed time.Duration
}
if err := resultsTemplate.Execute(w, templateData{ // HL
Results: results,
Elapsed: elapsed,
}); err != nil {
log.Print(err)
return
}
// ENDRENDER OMIT
}
// A Result contains the title and URL of a search result.
type Result struct { // HL
Title, URL string // HL
} // HL
var resultsTemplate = template.Must(template.New("results").Parse(`
<html>
<head/>
<body>
<ol>
{{range .Results}}
<li>{{.Title}} - <a href="{{.URL}}">{{.URL}}</a></li>
{{end}}
</ol>
<p>{{len .Results}} results in {{.Elapsed}}</p>
</body>
</html>
`))
// Search sends query to Google search and returns the results.
func Search(query string) ([]Result, error) {
// Prepare the Google Search API request.
u, err := url.Parse("https://ajax.googleapis.com/ajax/services/search/web?v=1.0")
if err != nil {
return nil, err
}
q := u.Query()
q.Set("q", query) // HL
u.RawQuery = q.Encode()
// Issue the HTTP request and handle the response.
resp, err := http.Get(u.String()) // HL
if err != nil {
return nil, err
}
defer resp.Body.Close() // HL
// Parse the JSON search result.
// https://developers.google.com/web-search/docs/#fonje
var jsonResponse struct {
ResponseData struct {
Results []struct {
TitleNoFormatting, URL string
}
}
}
if err := json.NewDecoder(resp.Body).Decode(&jsonResponse); err != nil { // HL
return nil, err
}
// Extract the Results from jsonResponse and return them.
var results []Result
for _, r := range jsonResponse.ResponseData.Results { // HL
results = append(results, Result{Title: r.TitleNoFormatting, URL: r.URL})
}
return results, nil
}
func handleSearch(w http.ResponseWriter, req *http.Request) { log.Println("serving", req.URL) // Check the search query. query := req.FormValue("q") if query == "" { http.Error(w, `missing "q" URL parameter`, http.StatusBadRequest) return }
FormValue
is a method on the type *http.Request
:
package http type Request struct {...} func (r *Request) FormValue(key string) string {...}
query := req.FormValue("q")
initializes a new variable query
with
the type of the expression on the right hand side, string
.
// Run the Google search. start := time.Now() results, err := Search(query) elapsed := time.Since(start) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return }
Search
returns two values, a slice of results and an error.
func Search(query string) ([]Result, error) {...}
The results are valid only if the error is nil.
type error interface { Error() string // a useful human-readable error message }
Errors may contain additional information, accessed via type assertions.
22// Render the results. type templateData struct { Results []Result Elapsed time.Duration } if err := resultsTemplate.Execute(w, templateData{ Results: results, Elapsed: elapsed, }); err != nil { log.Print(err) return }
resultsTemplate.Execute
generates HTML and writes it to an io.Writer
:
type Writer interface { Write(p []byte) (n int, err error) }
http.ResponseWriter
implements the io.Writer
interface.
// +build ignore,OMIT
// The server program issues Google search requests. It serves on port 8080.
//
// The /search endpoint accepts these query params:
// q=the Google search query
//
// For example, http://localhost:8080/search?q=golang serves the first
// few Google search results for "golang".
package main
import (
"encoding/json"
"fmt"
"html/template"
"log"
"net/http"
"net/url"
"time"
)
func main() {
http.HandleFunc("/search", handleSearch) // HL
fmt.Println("serving on http://localhost:8080/search")
log.Fatal(http.ListenAndServe("localhost:8080", nil))
}
// handleSearch handles URLs like "/search?q=golang" by running a
// Google search for "golang" and writing the results as HTML to w.
func handleSearch(w http.ResponseWriter, req *http.Request) {
log.Println("serving", req.URL)
// Check the search query.
query := req.FormValue("q") // HL
if query == "" {
http.Error(w, `missing "q" URL parameter`, http.StatusBadRequest)
return
}
// ENDQUERY OMIT
// Run the Google search.
start := time.Now()
results, err := Search(query) // HL
elapsed := time.Since(start)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// ENDSEARCH OMIT
// Render the results.
type templateData struct {
Results []Result
Elapsed time.Duration
}
if err := resultsTemplate.Execute(w, templateData{ // HL
Results: results,
Elapsed: elapsed,
}); err != nil {
log.Print(err)
return
}
// ENDRENDER OMIT
}
// A Result contains the title and URL of a search result. type Result struct { Title, URL string } var resultsTemplate = template.Must(template.New("results").Parse(` <html> <head/> <body> <ol> {{range .Results}} <li>{{.Title}} - <a href="{{.URL}}">{{.URL}}</a></li> {{end}} </ol> <p>{{len .Results}} results in {{.Elapsed}}</p> </body> </html> `))
// Search sends query to Google search and returns the results.
func Search(query string) ([]Result, error) {
// Prepare the Google Search API request.
u, err := url.Parse("https://ajax.googleapis.com/ajax/services/search/web?v=1.0")
if err != nil {
return nil, err
}
q := u.Query()
q.Set("q", query) // HL
u.RawQuery = q.Encode()
// Issue the HTTP request and handle the response.
resp, err := http.Get(u.String()) // HL
if err != nil {
return nil, err
}
defer resp.Body.Close() // HL
// Parse the JSON search result.
// https://developers.google.com/web-search/docs/#fonje
var jsonResponse struct {
ResponseData struct {
Results []struct {
TitleNoFormatting, URL string
}
}
}
if err := json.NewDecoder(resp.Body).Decode(&jsonResponse); err != nil { // HL
return nil, err
}
// Extract the Results from jsonResponse and return them.
var results []Result
for _, r := range jsonResponse.ResponseData.Results { // HL
results = append(results, Result{Title: r.TitleNoFormatting, URL: r.URL})
}
return results, nil
}
func Search(query string) ([]Result, error) { // Prepare the Google Search API request. u, err := url.Parse("https://ajax.googleapis.com/ajax/services/search/web?v=1.0") if err != nil { return nil, err } q := u.Query() q.Set("q", query) u.RawQuery = q.Encode() // Issue the HTTP request and handle the response. resp, err := http.Get(u.String()) if err != nil { return nil, err } defer resp.Body.Close()
The defer
statement arranges for resp.Body.Close
to run when Search
returns.
developers.google.com/web-search/docs/#fonje
var jsonResponse struct { ResponseData struct { Results []struct { TitleNoFormatting, URL string } } } if err := json.NewDecoder(resp.Body).Decode(&jsonResponse); err != nil { return nil, err } // Extract the Results from jsonResponse and return them. var results []Result for _, r := range jsonResponse.ResponseData.Results { results = append(results, Result{Title: r.TitleNoFormatting, URL: r.URL}) } return results, nil }
All the packages are from the standard library:
import ( "encoding/json" "fmt" "html/template" "log" "net/http" "net/url" "time" )
Go servers scale well: each request runs in its own goroutine.
Let's talk about concurrency.
27
Concurrent programs are structured as independent processes that
execute sequentially and communicate by passing messages.
Sequential execution is easy to understand. Async callbacks are not.
"Don't communicate by sharing memory, share memory by communicating."
Go primitives: goroutines, channels, and the select
statement.
Goroutines are like lightweight threads.
They start with tiny stacks and resize as needed.
Go programs can have hundreds of thousands of them.
Start a goroutine using the go
statement:
go f(args)
The Go runtime schedules goroutines onto OS threads.
Blocked goroutines don't use a thread.
29Channels provide communication between goroutines.
c := make(chan string) // goroutine 1 c <- "hello!" // goroutine 2 s := <-c fmt.Println(s) // "hello!"
A select
statement blocks until communication can proceed.
select { case n := <-in: fmt.Println("received", n) case out <- v: fmt.Println("sent", v) }
Only the selected case runs.
31Q: What does Google search do?
A: Given a query, return a page of search results (and some ads).
Q: How do we get the search results?
A: Send the query to Web search, Image search, YouTube, Maps, News, etc., then mix the results.
How do we implement this?
32We can simulate a Search function with a random timeout up to 100ms.
var ( Web = fakeSearch("web") Image = fakeSearch("image") Video = fakeSearch("video") ) type Search func(query string) Result func fakeSearch(kind string) Search { return func(query string) Result { time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) return Result(fmt.Sprintf("%s result for %q\n", kind, query)) } }
// +build ignore,OMIT
package main
import (
"fmt"
"math/rand"
"time"
)
type Result string
// START1 OMIT
func Google(query string) (results []Result) {
results = append(results, Web(query))
results = append(results, Image(query))
results = append(results, Video(query))
return
}
// STOP1 OMIT
// START2 OMIT
var (
Web = fakeSearch("web")
Image = fakeSearch("image")
Video = fakeSearch("video")
)
type Search func(query string) Result // HL
func fakeSearch(kind string) Search {
return func(query string) Result {
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
return Result(fmt.Sprintf("%s result for %q\n", kind, query))
}
}
// STOP2 OMIT
func init() {
rand.Seed(time.Now().UnixNano())
}
func main() { start := time.Now() results := Google("golang") elapsed := time.Since(start) fmt.Println(results) fmt.Println(elapsed) }
The Google function takes a query and returns a slice of Results (which are just strings).
Google invokes Web, Image, and Video searches serially, appending them to the results slice.
// +build ignore,OMIT
package main
import (
"fmt"
"math/rand"
"time"
)
type Result string
func Google(query string) (results []Result) { results = append(results, Web(query)) results = append(results, Image(query)) results = append(results, Video(query)) return }
// START2 OMIT
var (
Web = fakeSearch("web")
Image = fakeSearch("image")
Video = fakeSearch("video")
)
type Search func(query string) Result // HL
func fakeSearch(kind string) Search {
return func(query string) Result {
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
return Result(fmt.Sprintf("%s result for %q\n", kind, query))
}
}
// STOP2 OMIT
func init() {
rand.Seed(time.Now().UnixNano())
}
func main() {
start := time.Now()
results := Google("golang") // HL
elapsed := time.Since(start)
fmt.Println(results)
fmt.Println(elapsed)
}
Run the Web, Image, and Video searches concurrently, and wait for all results.
The func
literals are closures over query
and c
.
// +build ignore,OMIT
package main
import (
"fmt"
"math/rand"
"time"
)
type Result string
type Search func(query string) Result
var (
Web = fakeSearch("web")
Image = fakeSearch("image")
Video = fakeSearch("video")
)
func Google(query string) (results []Result) { c := make(chan Result) go func() { c <- Web(query) }() go func() { c <- Image(query) }() go func() { c <- Video(query) }() for i := 0; i < 3; i++ { result := <-c results = append(results, result) } return }
func fakeSearch(kind string) Search {
return func(query string) Result {
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
return Result(fmt.Sprintf("%s result for %q\n", kind, query))
}
}
func init() {
rand.Seed(time.Now().UnixNano())
}
func main() {
start := time.Now()
results := Google("golang")
elapsed := time.Since(start)
fmt.Println(results)
fmt.Println(elapsed)
}
Don't wait for slow servers.
No locks. No condition variables. No callbacks.
// +build ignore,OMIT
package main
import (
"fmt"
"math/rand"
"time"
)
type Result string
type Search func(query string) Result
var (
Web = fakeSearch("web")
Image = fakeSearch("image")
Video = fakeSearch("video")
)
func Google(query string) (results []Result) {
c := make(chan Result, 3) go func() { c <- Web(query) }() go func() { c <- Image(query) }() go func() { c <- Video(query) }() timeout := time.After(80 * time.Millisecond) for i := 0; i < 3; i++ { select { case result := <-c: results = append(results, result) case <-timeout: fmt.Println("timed out") return } } return
}
func fakeSearch(kind string) Search {
return func(query string) Result {
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
return Result(fmt.Sprintf("%s result for %q\n", kind, query))
}
}
func init() {
rand.Seed(time.Now().UnixNano())
}
func main() {
start := time.Now()
results := Google("golang")
elapsed := time.Since(start)
fmt.Println(results)
fmt.Println(elapsed)
}
Q: How do we avoid discarding results from slow servers?
A: Replicate the servers. Send requests to multiple replicas, and use the first response.
func First(query string, replicas ...Search) Result { c := make(chan Result, len(replicas)) searchReplica := func(i int) { c <- replicas[i](query) } for i := range replicas { go searchReplica(i) } return <-c }
// +build ignore,OMIT
package main
import (
"fmt"
"math/rand"
"time"
)
type Result string
type Search func(query string) Result
// START1 OMIT
func First(query string, replicas ...Search) Result {
c := make(chan Result, len(replicas))
searchReplica := func(i int) { c <- replicas[i](query) }
for i := range replicas {
go searchReplica(i)
}
return <-c
}
// STOP1 OMIT
func init() {
rand.Seed(time.Now().UnixNano())
}
func main() { start := time.Now() result := First("golang", fakeSearch("replica 1"), fakeSearch("replica 2")) elapsed := time.Since(start) fmt.Println(result) fmt.Println(elapsed) }
func fakeSearch(kind string) Search {
return func(query string) Result {
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
return Result(fmt.Sprintf("%s result for %q\n", kind, query))
}
}
Reduce tail latency using replicated search servers.
// +build ignore,OMIT
package main
import (
"fmt"
"math/rand"
"time"
)
type Result string
type Search func(query string) Result
var (
Web1 = fakeSearch("web1")
Web2 = fakeSearch("web2")
Image1 = fakeSearch("image1")
Image2 = fakeSearch("image2")
Video1 = fakeSearch("video1")
Video2 = fakeSearch("video2")
)
func Google(query string) (results []Result) {
c := make(chan Result, 3) go func() { c <- First(query, Web1, Web2) }() go func() { c <- First(query, Image1, Image2) }() go func() { c <- First(query, Video1, Video2) }() timeout := time.After(80 * time.Millisecond) for i := 0; i < 3; i++ { select { case result := <-c: results = append(results, result) case <-timeout: fmt.Println("timed out") return } } return
}
func First(query string, replicas ...Search) Result {
c := make(chan Result, len(replicas))
searchReplica := func(i int) {
c <- replicas[i](query)
}
for i := range replicas {
go searchReplica(i)
}
return <-c
}
func fakeSearch(kind string) Search {
return func(query string) Result {
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
return Result(fmt.Sprintf("%s result for %q\n", kind, query))
}
}
func init() {
rand.Seed(time.Now().UnixNano())
}
func main() {
start := time.Now()
results := Google("golang")
elapsed := time.Since(start)
fmt.Println(results)
fmt.Println(elapsed)
}
No locks. No condition variables. No callbacks.
41In just a few simple transformations we used Go's concurrency primitives to convert a
program into one that is
The language is designed for tooling.
44Gofmt formats code automatically. No options.
Goimports updates import statements based on your workspace.
Most people run these tools on save.
45
The go tool builds Go programs from source in a conventional directory layout.
No Makefiles or other configs.
Fetch the present
tool and its dependencies, build it, and install it:
% go get golang.org/x/tools/cmd/present
Run it:
% present
Generated documentation for the world's open-source Go code:
47Eclipse, IntelliJ, emacs, vim, many others.
gofmt
goimports
godoc
lookupsThere's no "Go IDE".
Go tools meet you where you are.
48Take the Go Tour online.
Lots more material.
Great community.
49