爲 Java 程序員準備的 Go 入門 PPT

爲 Java 程序員準備的 Go 入門 PPT

這是 Google 的 Go 團隊技術主管經理 Sameer Ajmani 分享的 PPT,爲 Java 程序員快速入門 Go 而準備的。javascript

視頻

這個 PPT 是 2015年4月23日在 NYJavaSIG 中使用的。css

前往 YouTube 觀看視頻
html

主要內容

1. Go 是什麼,誰在使用 Go?
2. 比較 Go 和 Java
3. 代碼示例
4. 併發
5. 工具前端

Go 是什麼?

「Go 是開源的編程語言,能夠很簡單的構建簡單,可靠和高效的軟件。」java

golang.orgnginx

Go 的歷史

從 2007 後半年開始設計git

  • Robert Griesemer, Rob Pike 和 Ken Thompson.
  • Ian Lance Taylor 和 Russ Cox

從 2009 年開始開源,有一個很是活躍的社區。程序員

Go 語言穩定版本 Go 1 是在 2012 年早期發佈的。golang

爲何有 Go?

Go 是解決 Google 規模的一個解決方案。web

系統規模

  • 規劃的規模爲 10⁶⁺ 臺機器
  • 天天在幾千臺機器上做業
  • 在系統中與其餘做業進行協做,交互
  • 同一時間進行大量工做

解決方案:對併發的支持很是強大

第二個問題:工程規模

在 2011 年

  • 跨 40+ 辦公室的 5000+ 名開發者
  • 每分鐘有 20+ 修改
  • 每月修改 50% 的代碼基礎庫
  • 天天執行 5千萬的測試用例
  • 單個代碼樹

解決方案:爲大型代碼基礎庫二設計的語言

誰在 Google 使用 Go?

大量的項目,幾千位 Go 程序員,百萬行的 Go 代碼。

公開的例子:

  • 移動設備的 Chrome SPDY 代理
  • Chrome, ChromeOS, Android SDK, Earth 等等的下載服務器
  • YouTube Vitess MySQL 均衡器

主要任務是網絡服務器,可是這是通用的語言。

除了 Google 還有誰在使用 Go?

golang.org/wiki/GoUsers

Apcera, 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, …

比較 Go 和 Java

Go 和 Java 有不少共同之處

  • C 系列 (強類型,括號)
  • 靜態類型
  • 垃圾收集
  • 內存安全 (nil 引用,運行時邊界檢查)
  • 變量老是初始化 (zero/nil/false)
  • 方法
  • 接口
  • 類型斷言 (實例)
  • 反射

Go 與 Java 的不一樣之處

  • 代碼程序直接編譯成機器碼,沒有 VM
  • 靜態連接二進制
  • 內存佈局控制
  • 函數值和詞法閉包
  • 內置字符串 (UTF-8)
  • 內置泛型映射和數組/片斷
  • 內置併發

Go 特地去掉了大量的特性

  • 沒有類
  • 沒有構造器
  • 沒有繼承
  • 沒有 final
  • 沒有異常
  • 沒有註解
  • 沒有自定義泛型

爲何 Go 要省去那些特性?

代碼清晰明瞭是首要的

當查看代碼時,能夠很清晰的知道程序將會作什麼

當編寫代碼的時候,也能夠很清晰的讓程序作你想作的

有時候這意味着編寫出一個循環而不是調用一個模糊的函數。

(不要變的太枯燥)

詳細的設計背景請看:

示例

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(` <html> <head/> <body>   <ol>   {{range .Results}}     <li>{{.Title}} - <href="{{.URL}}">{{.URL}}</a></li>   {{end}}   </ol>   <p>{{len .Results}} results in {{.Elapsed}}</p> </body> </html> `))

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

這就是它的前端

全部引用的包都來自標準庫:

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

其餘

沒有鎖,沒有條件變量,沒有調用。

總結

通過一些簡單轉換,咱們使用 Go 的併發原語來轉換一個

  • 順序性的
  • 故障敏感的

程序爲一個

  • 併發
  • 可複用的
  • 健壯的

工具

Go 有不少強大的工具

  • gofmt 和 goimports
  • The go tool
  • godoc
  • IDE 和編輯器支持

這語言就是爲工具鏈設計的。

gofmt 和 goimports

Gofmt 能夠自動格式化代碼,沒有選項。

Goimports 基於你的工做空間更新導入聲明

大部分人能夠安全的使用這些工具。

play.golang.org/p/GPqra77cBK

The go tool

The go tool 能夠在一個傳統目錄佈局中用源代碼構建 Go 程序。不須要 Makefiles 或者其餘配置。

匹配這些工具及其依賴,而後進行構建,安裝:

% go get golang.org/x/tools/cmd/present

運行:

% present

godoc

爲世界上全部的開源 Go 代碼生成文檔:

godoc.org

IDE 和編輯器支持

Eclipse, IntelliJ, emacs, vim 等等:

  • gofmt
  • goimports
  • godoclookups
  • code completion
  • code navigation

可是沒有 「Go IDE」.

Go 工具無處不在。

Go 的下一步計劃

Go 路線在線查看

tour.golang.org

大量的學習資料

golang.org/wiki/Learn

完美的社區

golang.org/project

相關文章
相關標籤/搜索