这是 Google 的 Go 团队技术主管经理 Sameer Ajmani 分享的 PPT,为 Java 程序员快速入门 Go 而准备的。
视频
|
1 2 3 4 5 | public class Main { public static void main(String[] args) { System.out.println( "Hello, world!" ); } } |
hello.go
1 2 3 4 5 | package main import "fmt" func main() { fmt.Println( "Hello, 世界!" ) } |
package main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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, 世界!" ) } |
(访问权限)类型根据变量名来声明。
公共变量名首字大写,私有变量首字母小写。
1 2 3 4 5 6 7 8 | 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) { |
1 2 3 4 5 6 7 8 | 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 的一个方法:
1 2 3 | package http type Request struct {...} func (r *Request) FormValue(key string) string {...} |
query := req.FormValue("q")初始化变量query,其变量类型是右边表达式的结果,这里是string类型.
1 2 3 4 5 6 7 8 | // 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.
1 | func Search(query string) ([]Result, error) {...} |
当error的值为nil时,results有效。
1 2 3 | type error interface { Error() string // a useful human-readable error message } |
Error类型可能包含额外的信息,可通过断言访问。
1 2 3 4 5 6 7 8 9 10 11 12 | // 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:
1 2 3 | type Writer interface { Write(p []byte) (n int , err error) } |
http.ResponseWriter实现了io.Writer接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 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> `)) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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方法返回时。
developers.google.com/web-search/docs/#fonje
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 } |
所有引用的包都来自标准库:
1 2 3 4 5 6 7 8 9 | import ( "encoding/json" "fmt" "html/template" "log" "net/http" "net/url" "time" ) |
Go服务器规模:每一个请求都运行在自己的goroutine里。
让我们谈谈并发。
并发程序作为独立进程,通过信息交流的顺序执行。
顺序执行很容易理解,异步则不是。
“不要为共亨内存通信,为通信共享内存。”
Go原理: goroutines, channels, 和 select声明.
Goroutines 就像轻量级线程。
它们通过小栈(tiny stacks)和按需调整运行。
Go 程序可以拥有成千上万个(goroutines)实例
使用go声明启动一个goroutines:
1 | go f(args) |
Go运行时把goroutines放进OS线程里。
不要使用线程堵塞goroutines。
Channels被定义是为了与goroutines之间通信。
1 2 3 4 5 6 7 8 | c := make (chan string) // goroutine 1 c <- "hello!" // goroutine 2 s := <-c fmt .Println(s) // "hello!" |
select声明一个语句块来判断执行。
1 2 3 4 5 6 | select { case n := <-in: fmt.Println( "received" , n) case out <- v: fmt.Println( "sent" , v) } |
只有条件成立的case块会运行。
问: Google搜索能做些什么?
答: 提出一个问题,它可以返回一个搜索结果的页面(和一些广告)。
问: 我们怎么得到这些搜索结果?
答: 发送一个问题到网页搜索、图片搜索、YouTube(视频)、地图、新闻,稍等然后检索出结果。
我们该怎么实现它?
We can simulate a Search function with a random timeout up to 100ms.
我们要模拟一个搜索函数,让它随机超时0到100毫秒。
1 2 3 4 5 6 7 8 9 10 11 12 | 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)) } } |
1 2 3 4 5 6 7 | func main() { start := time .Now() results := Google( "golang" ) elapsed := time .Since(start) fmt.Println(results) fmt.Println(elapsed) } |
Google函数获取一个查询,然后返回一个的结果集 (不一定是字符串).
Google按顺序调用Web(网页)、Image(图片)、Video(视频)并将返回加入到结果集中。
1 2 3 4 5 6 | func Google(query string) (results []Result) { results = append(results, Web(query)) results = append(results, Image(query)) results = append(results, Video(query)) return } |
同时执行 Web,、Image、 和Video搜索,并等待所有结果。
func方法是在query和c的地方关闭的。
1 2 3 4 5 6 7 8 9 10 11 | 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 } |
等待慢的服务器。
没有锁,没有条件变量,没有返回值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 |
问: 如何防止丢掉慢的服务的结果?
答: 复制这个服务,然后发送请求到多个复制的服务,并使用第一个响应的结果。
1 2 3 4 5 6 7 8 | 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 } |
1 2 3 4 5 6 7 8 9 | func main() { start := time .Now() result := First( "golang" , fakeSearch( "replica 1" ), fakeSearch( "replica 2" )) elapsed := time .Since(start) fmt.Println(result) fmt.Println(elapsed) } |
使用复制的服务以减少多余延迟。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 的并发原语来转换一个
慢
顺序性的
故障敏感的
程序为一个
快
并发
可复用的
健壮的
gofmt 和 goimports
The go tool
godoc
IDE 和编辑器支持
这语言就是为工具链设计的。
Gofmt 可以自动格式化代码,没有选项。
Goimports 基于你的工作空间更新导入声明
大部分人可以安全的使用这些工具。
The go tool 可以在一个传统目录布局中用源代码构建 Go 程序。不需要 Makefiles 或者其他配置。
匹配这些工具及其依赖,然后进行构建,安装:
1 | % go get golang.org /x/tools/cmd/present |
运行:
1 | % present |
为世界上所有的开源 Go 代码生成文档:
Eclipse, IntelliJ, emacs, vim 等等:
gofmt
goimports
godoclookups
code completion
code navigation
但是没有 "Go IDE".
Go 工具无处不在。
Go 路线在线查看
大量的学习资料
完美的社区
Sameer Ajmani
Tech Lead Manager, Go team