为 Java 程序员准备的 Go 入门 PPT

1,315 阅读7分钟
原文链接: www.oschina.net

Java程序猿对Go应该很眼熟

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, 世界!")
}

Hello, web server(你好,web服务)

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, 世界!")
}

(访问权限)类型根据变量名来声明。
公共变量名首字大写,私有变量首字母小写。

示例:Google搜索前端

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) {

请求验证

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
    }

FormValueis 是 *http.Request 的一个方法:

package http
type Request struct {...}
func (r *Request) FormValue(key string) string {...}

query := req.FormValue("q")初始化变量query,其变量类型是右边表达式的结果,这里是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方法有两个返回值,分别为结果results和错误error.

func Search(query string) ([]Result, error) {...}

当error的值为nil时,results有效。

type error interface {
    Error() string // a useful human-readable error message
}

Error类型可能包含额外的信息,可通过断言访问。 

渲染搜索结果

// 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
    }

结果results使用Template.Execute生成HTML,并存入一个io.Writer:

type Writer interface {
        Write(p []byte) (n int, err error)
}

http.ResponseWriter实现了io.Writer接口。

Go变量操作HTML模板

// 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(`



  
      {{range .Results}}     
  1. {{.Title}} - {{.URL}}
  2.   {{end}}   
  

{{len .Results}} results in {{.Elapsed}}

`))

请求Google搜索API

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()

defer声明使resp.Body.Close运行在Search方法返回时。

解析返回的JSON数据到Go struct类型

developers.google.com/web-search/…

  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
}

这就是它的前端

所有引用的包都来自标准库:

import (
    "encoding/json"
    "fmt"
    "html/template"
    "log"
    "net/http"
    "net/url"
    "time"
)

Go服务器规模:每一个请求都运行在自己的goroutine里。

让我们谈谈并发。

通信顺序进程(Hoare,1978)

并发程序作为独立进程,通过信息交流的顺序执行。

顺序执行很容易理解,异步则不是。

“不要为共亨内存通信,为通信共享内存。”

Go原理: goroutines, channels, 和 select声明.

Goroutines

Goroutines 就像轻量级线程。

它们通过小栈(tiny stacks)和按需调整运行。

Go 程序可以拥有成千上万个(goroutines实例

使用go声明启动一个goroutines:

go f(args)

Go运行时把goroutines放进OS线程里。

不要使用线程堵塞goroutines。

Channels

Channels被定义是为了与goroutines之间通信。

c := make(chan string)

// goroutine 1
c <- "hello!"

// goroutine 2
s := <-c
fmt.Println(s) // "hello!"

Select

select声明一个语句块来判断执行。

select {
case n := <-in:
  fmt.Println("received", n)
case out <- v:
  fmt.Println("sent", v)
}

只有条件成立的case块会运行。

示例:Google搜索(后端)

问: Google搜索能做些什么?

答: 提出一个问题,它可以返回一个搜索结果的页面(和一些广告)。

问: 我们怎么得到这些搜索结果?

答: 发送一个问题到网页搜索、图片搜索、YouTube(视频)、地图、新闻,稍等然后检索出结果。

我们该怎么实现它?

Google搜索 : 一个假的框架

We can simulate a Search function with a random timeout up to 100ms.

我们要模拟一个搜索函数,让它随机超时0到100毫秒。

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))
    }
}

Google搜索: 测试框架

func main() {
    start := time.Now()
    results := Google("golang")
    elapsed := time.Since(start)
    fmt.Println(results)
    fmt.Println(elapsed)
}

Google搜索 (串行)

Google函数获取一个查询,然后返回一个的结果集 (不一定是字符串).

Google按顺序调用Web(网页)、Image(图片)、Video(视频)并将返回加入到结果集中。

func Google(query string) (results []Result) {
    results = append(results, Web(query))
    results = append(results, Image(query))
    results = append(results, Video(query))
    return
}

Google搜索(并行)

同时执行 Web,、Image、 和Video搜索,并等待所有结果。

func方法是在query和c的地方关闭的。

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
}

Google搜索 (超时)

等待慢的服务器。

没有锁,没有条件变量,没有返回值。

    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 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
}

使用First函数

func main() {
    start := time.Now()
    result := First("golang",
        fakeSearch("replica 1"),
        fakeSearch("replica 2"))
    elapsed := time.Since(start)
    fmt.Println(result)
    fmt.Println(elapsed)
}

Google搜索 (复制)

使用复制的服务以减少多余延迟。

    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