| <html> |
| <head> |
| <meta http-equiv="Content-Type" content="text/html;charset=utf-8" > |
| <title>A Tour of Go</title> |
| |
| <!-- jQuery --> |
| <script src="/static/jquery.js"></script> |
| |
| <!-- Fonts --> |
| <link href='http://fonts.googleapis.com/css?family=Droid+Serif' rel='stylesheet' type='text/css'> |
| <link href='http://fonts.googleapis.com/css?family=Droid+Sans' rel='stylesheet' type='text/css'> |
| <link href='http://fonts.googleapis.com/css?family=Droid+Sans+Mono' rel='stylesheet' type='text/css'> |
| |
| <!-- Playground --> |
| <link rel="stylesheet" href="/static/codemirror/lib/codemirror.css"> |
| <script src="/static/codemirror/lib/codemirror.js"></script> |
| <link rel="stylesheet" href="/static/codemirror/theme/default.css"> |
| <script src="/static/playground.js"></script> |
| |
| <!-- Tour --> |
| <link rel="stylesheet" href="/static/tour.css" charset="utf-8"> |
| <script src="/static/mode.js"></script> |
| <script src="/static/tour.js"></script> |
| |
| </head> |
| <body class="loading"> |
| |
| <!-- Top bar --> |
| <h2 id="slidenum"></h2> |
| <div id="topnav" class="nav"> |
| <button id="tocbtn">INDEX</button> |
| </div> |
| <h1>A Tour of Go</h1> |
| |
| <!-- Loading message --> |
| <div id="loading"> |
| Loading slides... |
| </div> |
| |
| <!-- Table of Contents --> |
| <ol id="toc"></ol> |
| |
| <div id="slides" class="slides"><!-- begin slides --> |
| |
| <div class="toc">Welcome</div> |
| |
| <div class="slide nocode"> |
| <h2>Let's Go</h2> |
| <center> |
| <p>Sameer Ajmani |
| <p>Google NYC Tech Talk Meetup |
| <p>April 25, 2012 |
| </center> |
| </div> |
| |
| <div class="slide nocode appengineMode"> |
| <h2>Go offline</h2> |
| <p> |
| This talk contains several examples that will only work if you run the |
| gotour stand-alone program on your local machine. You can flip through |
| the following slides to read the talk and code, but most of the examples |
| will fail mysteriously if you're |
| using <a href="http://tour.golang.org">tour.golang.org</a>. |
| <p> |
| To run the talk locally first |
| <a target="_blank" href="http://golang.org/doc/install/">install Go</a>, |
| then use |
| <a target="_blank" href="http://golang.org/cmd/go/">go get</a> to install |
| <a target="_blank" href="http://code.google.com/p/go-tour/">gotour</a>: |
| <pre> go get code.google.com/p/go-tour/gotour</pre> |
| <p> |
| and run the resultant <code>gotour</code> executable. Finally, navigate |
| to <a href="http://127.0.0.1:3999/talks/2012-04-25-NewYorkMeetup"> |
| http://127.0.0.1:3999/talks/2012-04-25-NewYorkMeetup</a> |
| to access this talk locally. |
| <p> |
| Otherwise, click the "next" button or type PageDown to continue. |
| <p> |
| <i>(You may return to these instructions at any time by clicking the |
| "index" button.)</i> |
| </div> |
| |
| <div class="slide nocode"> |
| <h2>Why Go?</h2> |
| |
| <p>Statically typed languages are efficient, but typically bureaucratic and |
| overly complex. |
| |
| <p>Dynamic languages can be easy to use, but are error-prone, inefficient, and |
| break down at scale. |
| |
| <p>Concurrent programming is hard (threads, locks, headache). |
| |
| <p>"Speed, reliability, simplicity: pick two." (sometimes just one) |
| |
| <p>Can't we do better? |
| </div> |
| |
| <div class="slide nocode"> |
| <h2>Go 1</h2> |
| |
| <p>Go began in September 2007. |
| <blockquote>"Consensus drove the design. Nothing went into the language |
| until [Ken Thompson, Robert Griesemer, and myself] all agreed |
| that it was right. Some features didn't get resolved until after |
| a year or more of discussion." -- Rob Pike</blockquote> |
| <p>The first release of Go was in November 2009. |
| <p>Go version 1 (Go 1) was released March 28, 2012. |
| </div> |
| |
| <div class="slide nocode"> |
| <h2>What is Go?</h2> |
| |
| <p>Go is ... |
| <ul> |
| <li>a general-purpose, open source programming language |
| <li>concise, expressive, and readable |
| <li>statically-typed, with "duck typing" via implicit interfaces |
| <li>garbage-collected |
| <li>compiled to native machine code: compiles fast, runs fast |
| <li>especially good for concurrent programming |
| </ul> |
| <p>Start at <a href="http://golang.org">http://golang.org</a> |
| </div> |
| |
| <div class="slide"> |
| <h2>Hello, 世界</h2> |
| <p>Here's "Hello, world" in Go. |
| <p>I'm running a modified version of <a href="http://tour.golang.org">http://tour.golang.org</a>. |
| <div> |
| package main |
| |
| import "fmt" |
| |
| func main() { |
| fmt.Println("Hello, 世界") |
| } |
| </div> |
| </div> |
| |
| <div class="slide"> |
| <h2>Hello, world 2.0</h2> |
| <p>Serving "Hello, world" |
| at <a href="http://localhost:8080/world">http://localhost:8080/world</a> |
| |
| <p><code>import "net/http"</code> imports the named package as <code>http</code>;<br> |
| <code>http.HandleFunc</code> references <code>HandleFunc</code> from that package. |
| |
| <p>In <code>handler</code>, what's the type of <code>name</code>? |
| <div> |
| package main |
| |
| import ( |
| "fmt" |
| "net/http" |
| ) |
| |
| func main() { |
| http.HandleFunc("/", handler) |
| http.ListenAndServe(":8080", nil) |
| } |
| |
| func handler(w http.ResponseWriter, |
| r *http.Request) { |
| name := r.URL.Path[1:] |
| fmt.Fprint(w, "Hello, "+ name) |
| } |
| </div> |
| </div> |
| |
| <div class="slide"> |
| <h2>Simple type system</h2> |
| <p>Go is statically typed, but type inference saves repitition. |
| |
| <p>Java:<br> |
| <code>Date now = new Date();</code> |
| |
| <p>C/C++:<br> |
| <code>time_t now = time(NULL);</code> |
| |
| <p>Go:<br> |
| <code>now := time.Now()</code><br> |
| <code>i := 1</code><br> |
| <code>pi := math.Pi</code><br> |
| <code>greeting := "Hello!"</code><br> |
| |
| <div> |
| package main |
| |
| import "fmt" |
| |
| var w int |
| |
| var ( |
| x = 1 |
| y, z = 2, 3 |
| ) |
| |
| func main() { |
| var c = true |
| python, java := false, "no!" |
| fmt.Println(w, x, y, z, c, python, java) |
| } |
| </div> |
| </div> |
| |
| <div class="slide"> |
| <h2>Types and Functions</h2> |
| <p>Declare a type with <code>type</code>, and declare a function |
| with <code>func</code>. |
| <div> |
| package main |
| |
| import "fmt" |
| |
| type Vertex struct { |
| X, Y float64 |
| } |
| |
| func VertexToString(v Vertex) string { |
| return fmt.Sprintf("(%f, %f)", v.X, v.Y) |
| } |
| |
| func main() { |
| var v Vertex |
| v.X, v.Y = 3, 4 |
| fmt.Println(VertexToString(v)) |
| } |
| </div> |
| </div> |
| |
| <div class="slide"> |
| <h2>Composite Literals</h2> |
| <p> |
| Go provides excellent composite literals. These makes it easy to |
| declare values for structs, maps, arrays, and slices, including nested |
| values. |
| <div> |
| package main |
| |
| import "fmt" |
| |
| type Vertex struct { |
| X, Y float64 |
| } |
| |
| func VertexToString(v Vertex) string { |
| return fmt.Sprintf("(%f, %f)", v.X, v.Y) |
| } |
| |
| func main() { |
| v := Vertex{3, 4} |
| fmt.Println(VertexToString(v)) |
| } |
| </div> |
| </div> |
| |
| <div class="slide"> |
| <h2>Methods</h2> |
| <p> |
| Go does not have classes. However, you can define methods on struct |
| types. |
| <p> |
| The <i>method receiver</i> appears as an argument between |
| the <code>func</code> keyword and the method name. |
| <div> |
| package main |
| |
| import "fmt" |
| |
| type Vertex struct { |
| X, Y float64 |
| } |
| |
| func (v Vertex) String() string { |
| return fmt.Sprintf("(%f, %f)", v.X, v.Y) |
| } |
| |
| func main() { |
| v := Vertex{3, 4} |
| fmt.Println(v.String()) |
| } |
| </div> |
| </div> |
| |
| <div class="slide"> |
| <h2>Methods</h2> |
| <p> |
| In fact, you can define a method on <i>any</i> type you define in your |
| package, not just structs. |
| <p> |
| You cannot define a method on a type from another package, or on a |
| basic type. |
| <div> |
| package main |
| |
| import "fmt" |
| |
| type Celsius float32 |
| type Fahrenheit float32 |
| |
| func (c Celsius) String() string { |
| return fmt.Sprintf("%.2f°C", c) |
| } |
| |
| func (f Fahrenheit) String() string { |
| return fmt.Sprintf("%.2f°F", f) |
| } |
| |
| func (c Celsius) Fahrenheit() Fahrenheit { |
| return Fahrenheit(c*9/5 + 32) // conversion |
| } |
| |
| func main() { |
| c := Celsius(100) |
| fmt.Println(c, "==", c.Fahrenheit()) |
| |
| |
| // Celsius, Fahrenheit, and float32 |
| // are different types! |
| // tryMe := c + Fahrenheit(32) |
| } |
| </div> |
| </div> |
| |
| <div class="slide"> |
| <h2>Interfaces</h2> |
| <p> |
| Did you notice I forgot to type <code>c.String()</code> in |
| the <code>fmt</code> calls on the previous slide? The <code>fmt</code> |
| package automatically calls <code>String</code> on anything that |
| implements <code>fmt.Stringer</code>. |
| <p> |
| But I didn't have to say "implements Stringer" anywhere. |
| <p> |
| <i>There is no explicit declaration of intent.</i> |
| <p> |
| An interface type is defined by a set of methods. |
| <p> |
| A value of interface type can hold any value that |
| implements those methods. |
| <p> |
| This decouples implementation packages from the packages |
| that define the interfaces: neither depends on the other. |
| <div> |
| package main |
| |
| import "fmt" |
| |
| type Stringer interface { |
| String() string |
| } |
| |
| func main() { |
| var s Stringer |
| v := Vertex{3, 4} |
| c := Celsius(100) |
| |
| s = v // Vertex implements Stringer |
| s = c // Celsius implements Stringer |
| fmt.Println(v, c, s) |
| } |
| |
| type Vertex struct { X, Y float64 } |
| |
| func (v Vertex) String() string { |
| return fmt.Sprintf("(%f, %f)", v.X, v.Y) |
| } |
| |
| type Celsius float32 |
| |
| func (c Celsius) String() string { |
| return fmt.Sprintf("%.2f°C", c) |
| } |
| |
| </div> |
| </div> |
| |
| <div class="slide"> |
| <h2>Hello, world 2.1</h2> |
| <p>Serving "Hello, world" |
| at <a href="http://localhost:8081/world">http://localhost:8081/world</a> |
| <p> |
| <a target="_blank" href="http://golang.org/pkg/net/http/">Package http</a> serves HTTP requests using any value |
| that implements <code>http.Handler</code>: |
| <pre> |
| package http |
| |
| type Handler interface { |
| ServeHTTP(w ResponseWriter, |
| r *Request) |
| } |
| </pre> |
| <p>And <code>fmt.Fprint</code> can write to any value that implements <code>io.Writer</code>: |
| <pre> |
| package io |
| |
| type Writer interface { |
| Write(b []byte) (n int, err error) |
| } |
| </pre> |
| In this example, the type <code>Hello</code> |
| implements <code>http.Handler</code>, and the |
| type <code>http.ResponseWriter</code> |
| implements </code>io.Writer</code>. |
| <p> |
| <code>Hello</code> can contain state, but beware: calls to ServeHTTP may |
| run concurrently. |
| <div> |
| package main |
| |
| import ( |
| "fmt" |
| "net/http" |
| ) |
| |
| type Hello struct { |
| greeting string |
| } |
| |
| func (h Hello) ServeHTTP(w http.ResponseWriter, |
| r *http.Request) { |
| name := r.URL.Path[1:] |
| fmt.Fprint(w, h.greeting, ", ", name) |
| } |
| |
| func main() { |
| h := Hello{"Namaste"} |
| http.ListenAndServe("localhost:8081", h) |
| } |
| </div> |
| </div> |
| |
| <div class="slide nocode"> |
| <h2>Concurrency</h2> |
| |
| <p>Based on Communicating Sequential Processes (CSP), first described in 1978 by |
| C.A.R. Hoare. |
| |
| <p>In UNIX we think about <b>processes</b> connected by <b>pipes</b>: |
| <p><code>find ~/go/src/pkg | grep _test.go$ | xargs wc -l</code> |
| <p>Each tool is designed to do one thing and to do it well. |
| |
| <p>In the real world, we assign tasks to <b>individuals</b> connected by <b>communication</b>:<br> |
| factory assembly lines, phone trees, software projects. |
| |
| <p>The Go analogue: <b>goroutines</b> connected by <b>channels</b>. |
| </div> |
| |
| <div class="slide nocode"> |
| <h2>Goroutines</h2> |
| <p> |
| Goroutines are like threads: |
| <ul> |
| <li>They share memory.</li> |
| </ul> |
| But cheaper: |
| <ul> |
| <li>Smaller, segmented stacks.</li> |
| <li>Many goroutines per operating system thread.</li> |
| <li>Managed by the Go runtime.</li> |
| </ul> |
| </div> |
| |
| <div class="slide"> |
| <h2>Goroutines</h2> |
| <p> |
| Start a new goroutine with the <code>go</code> keyword: |
| <pre>go f(x, y, z)</pre> |
| <p> |
| starts a new goroutine running |
| <pre>f(x, y, z)</pre> |
| <p> |
| The evaluation |
| of <code>f</code>, <code>x</code>, <code>y</code>, and <code>z</code> |
| happens in the current goroutine and the execution of <code>f</code> |
| happens in the new goroutine. |
| <p> |
| Goroutines run in the same address space, so access to shared memory |
| must be synchronized. The <code><a href="http://golang.org/pkg/sync/" |
| target="_blank">sync</a></code> package provides useful primitives, |
| but you don't need them so much in Go thanks to channels. |
| <div> |
| package main |
| |
| import ( |
| "fmt" |
| "math/rand" |
| "time" |
| ) |
| |
| func say(s string) { |
| for i := 0; i < 5; i++ { |
| time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) |
| fmt.Println(s) |
| } |
| } |
| |
| func main() { |
| rand.Seed(time.Now().UnixNano()) |
| go say(" world") |
| say("hello") |
| } |
| </div> |
| </div> |
| |
| <div class="slide"> |
| <h2>Channels</h2> |
| |
| <p> |
| A channel is a typed conduit through which you can send and receive |
| values with the channel operator, <code><-</code>. |
| <pre> |
| ch <- v // Send v to channel ch. |
| v := <-ch // Receive from ch, and |
| // assign value to v. |
| </pre> |
| <p> |
| (The data flows in the direction of the arrow.) |
| |
| <p> |
| Channels must be created using <code>make</code>: |
| <pre> |
| ch := make(chan int) |
| </pre> |
| <p> |
| By default, sends and receives block until the other side is ready. |
| This allows goroutines to synchronize without explicit locks or |
| condition variables. |
| <div> |
| package main |
| |
| import "fmt" |
| |
| func sum(a []int, c chan int) { |
| sum := 0 |
| for i := 0; i < len(a); i++ { |
| sum += a[i] |
| } |
| c <- sum // send sum to c |
| } |
| |
| func main() { |
| a := []int{7, 2, 8, -9, 4, 0} |
| |
| c := make(chan int) |
| go sum(a[:len(a)/2], c) |
| go sum(a[len(a)/2:], c) |
| x, y := <-c, <-c // receive from c |
| |
| fmt.Println(x, y, x + y) |
| } |
| </div> |
| </div> |
| |
| |
| <div class="slide"> |
| <h2>Buffered Channels</h2> |
| |
| <p> |
| Channels can be <i>buffered</i>. Provide the buffer length as the |
| second argument to <code>make</code> to initialize a buffered channel: |
| <pre> |
| ch := make(chan int, 100) |
| </pre> |
| |
| <p> |
| Sends to a buffered channel block only when the buffer is full. |
| Receives block when the buffer is empty. |
| <div> |
| package main |
| |
| import "fmt" |
| |
| func main() { |
| c := make(chan int, 2) |
| c <- 1 |
| c <- 2 |
| fmt.Println(<-c) |
| fmt.Println(<-c) |
| } |
| </div> |
| </div> |
| |
| <div class="slide"> |
| <h2>Range and Close</h2> |
| <p> |
| A sender can <code>close</code> a channel to indicate that no more |
| values will be sent. Receivers can test whether a channel has been |
| closed by assigning a second parameter to the receive expression: after |
| <pre> |
| v, ok := <-ch</pre> |
| <p> |
| <code>ok</code> is <code>false</code> if there are no more values to |
| receive and the channel is closed. |
| <p> |
| The loop <code>for i := range c</code> receives values from the |
| channel repeatedly until it is closed. |
| <p> |
| |
| <div> |
| package main |
| |
| import "fmt" |
| |
| func fibonacci(n int, c chan int) { |
| x, y := 1, 1 |
| for i := 0; i < n; i++ { |
| c <- x |
| x, y = y, x + y |
| } |
| close(c) |
| } |
| |
| func main() { |
| c := make(chan int, 10) |
| go fibonacci(cap(c), c) |
| for i := range c { |
| fmt.Println(i) |
| } |
| } |
| </div> |
| </div> |
| |
| <div class="slide"> |
| <h2>Select</h2> |
| <p> |
| The <code>select</code> statement lets a goroutine wait on multiple |
| communication operations. |
| <p> |
| A <code>select</code> blocks until one of its cases can run, then it |
| executes that case. It chooses one at random if multiple are ready. |
| <p> |
| A <code>select</code> can have a <code>default</code> case that runs if |
| the others would block. This lets you write non-blocking channel ops: |
| <pre> |
| select { |
| case c1 <- x: |
| // sent x |
| case y := <-c2: |
| // received y |
| default: |
| // nothing ready |
| } |
| </pre> |
| <div> |
| package main |
| |
| import "fmt" |
| |
| func fibonacci(c, quit chan int) { |
| x, y := 1, 1 |
| for { |
| select { |
| case c <- x: |
| x, y = y, x + y |
| case <-quit: |
| fmt.Println("quit") |
| return |
| } |
| } |
| } |
| |
| func main() { |
| c := make(chan int) |
| quit := make(chan int) |
| go fibonacci(c, quit) |
| for i := 0; i < 10; i++ { |
| fmt.Println(<-c) |
| } |
| quit <- 0 |
| } |
| </div> |
| </div> |
| |
| <div class="slide nocode"> |
| <h2>Example: Google Search</h2> |
| <p>Q: What does Google search do? |
| |
| <p>A: Given a query, return a page of search results (and some ads). |
| |
| <p>Q: How do we get the search results? |
| |
| <p>A: Send the query to Web search, Image search, YouTube, Maps, News, |
| etc., then mix the results. |
| |
| <p>How do we implement this? |
| </div> |
| |
| |
| <div class="slide"> |
| <h2>Example: Google Search 1.0</h2> |
| |
| <p>The <code>Google</code> function takes a query and returns a slice |
| of <code>Results</code>. |
| |
| <p><code>Google</code> |
| invokes <code>Web</code>, <code>Image</code>, |
| and <code>Video</code> searches serially, appending them to |
| the <code>results</code> slice. |
| <div> |
| 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) { |
| results = append(results, Web(query)) |
| results = append(results, Image(query)) |
| results = append(results, Video(query)) |
| 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 main() { |
| rand.Seed(time.Now().UnixNano()) |
| start := time.Now() |
| results := Google("golang") |
| elapsed := time.Since(start) |
| fmt.Println(results) |
| fmt.Println(elapsed) |
| } |
| </div> |
| </div> |
| |
| <div class="slide"> |
| <h2>Example: Google Search 2.1</h2> |
| |
| <p><code>Google</code> invokes <code>Web</code>, <code>Image</code>, |
| and <code>Video</code> searches concurrently, and waits for the expected |
| number of results. |
| |
| <p>No locks. No condition variables. No callbacks. |
| <div> |
| 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) } () |
| |
| for i := 0; i < cap(c); 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 main() { |
| rand.Seed(time.Now().UnixNano()) |
| start := time.Now() |
| results := Google("golang") |
| elapsed := time.Since(start) |
| fmt.Println(results) |
| fmt.Println(elapsed) |
| } |
| </div> |
| </div> |
| |
| <div class="slide"> |
| <h2>Example: Google Search 2.2</h2> |
| |
| <p>Don't wait for slow backends. |
| |
| <p>No locks. No condition variables. No callbacks. |
| <div> |
| 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 < cap(c); 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 main() { |
| rand.Seed(time.Now().UnixNano()) |
| start := time.Now() |
| results := Google("golang") |
| elapsed := time.Since(start) |
| fmt.Println(results) |
| fmt.Println(elapsed) |
| } |
| </div> |
| </div> |
| |
| |
| <div class="slide nocode"> |
| <h2>Example: Query failover</h2> |
| <p>Q: How do we avoid timing out our requests to slow backends? |
| |
| <p>A: Replicate the backends. Send requests to multiple replicas, and |
| use the first response. |
| </div> |
| |
| <div class="slide"> |
| <h2>Example: Query failover</h2> |
| |
| <p><code>First</code> runs searches on the replicas |
| concurrently and returns the first result. |
| <div> |
| package main |
| |
| import ( |
| "fmt" |
| "math/rand" |
| "time" |
| ) |
| |
| type Result string |
| type Search func(query string) Result |
| |
| 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 main() { |
| rand.Seed(time.Now().UnixNano()) |
| start := time.Now() |
| result := First("query", |
| 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)) |
| } |
| } |
| </div> |
| </div> |
| |
| <div class="slide"> |
| <h2>Example: Google Search 3.0</h2> |
| |
| <p>Reduces tail latency using replicated search backends. |
| |
| <div> |
| 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 < cap(c); 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 main() { |
| rand.Seed(time.Now().UnixNano()) |
| start := time.Now() |
| results := Google("golang") |
| elapsed := time.Since(start) |
| fmt.Println(results) |
| fmt.Println(elapsed) |
| } |
| |
| </div> |
| </div> |
| |
| <div class="slide nocode"> |
| <h2>There's a lot more to Go</h2> |
| <ul> |
| <li>arrays, maps, slices</li> |
| <li>struct and interface embedding</li> |
| <li>defer, panic, recover</li> |
| <li>type switches and reflection</li> |
| <li>the go tool</li> |
| </ul> |
| </div> |
| |
| |
| <div class="slide nocode"> |
| <h2>Where to Go from here...</h2> |
| <ul> |
| <li><a href="http://golang.org">http://golang.org</a> - Go Home Page</li> |
| <li><a href="http://golang.org/ref/spec">http://golang.org/ref/spec</a> |
| - Language Spec</li> |
| <li><a href="http://golang.org/pkg">http://golang.org/pkg</a> - |
| Package Documentation</li> |
| <li><a href="http://golang.org/doc/codewalk">http://golang.org/doc/codewalk</a> - Codewalks</li> |
| <ul> |
| <li>Share Memory by Communicating - Go concurrency model</li> |
| <li>First Class Functions in Go - Go's function types</li> |
| </ul> |
| <li><a href="http://blog.golang.org">http://blog.golang.org</a> - |
| especially "Defer, Panic, and Recover".</li> |
| </ul> |
| </div> |
| |
| </div><!-- end slides --> |
| |
| </body> |
| </html> |