这是 Google 的 Go 团队技术主管经理 Sameer Ajmani 分享的 PPT,为 Java 程序员快速入门 Go 而准备的。
这个 PPT 是 2015年4月23日在 NYJavaSIG 中使用的。
前往 YouTube 观看视频
1. Go 是什么,谁在使用 Go?
2. 比较 Go 和 Java
3. 代码示例
4. 并发
5. 工具
“Go 是开源的编程语言,可以很简单的构建简单,可靠和高效的软件。”
golang.org
从 2007 后半年开始设计
从 2009 年开始开源,有一个非常活跃的社区。
Go 语言稳定版本 Go 1 是在 2012 年早期发布的。
Go 是解决 Google 规模的一个解决方案。
解决方案:对并发的支持非常强大
在 2011 年
解决方案:为大型代码基础库而设计的语言
大量的项目,几千位 Go 程序员,百万行的 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, …
代码清晰明了是首要的
当查看代码时,可以很清晰的知道程序将会做什么
当编写代码的时候,也可以很清晰的让程序做你想做的
有时候这意味着编写出一个循环而不是调用一个模糊的函数。
(不要变的太枯燥)
详细的设计背景请看:
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, 世界!") }
(访问权限)类型根据变量名来声明。公共变量名首字大写,私有变量首字母小写。
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接口。
// 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> `))
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
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里。
让我们谈谈并发。
并发程序作为独立进程,通过信息交流的顺序执行。
顺序执行很容易理解,异步则不是。
“不要为共亨内存通信,为通信共享内存。”
Go原理:goroutines, channels, 和 select声明.
Goroutines 就像轻量级线程。
它们通过小栈(tiny stacks)和按需调整运行。
Go 程序可以拥有成千上万个(goroutines)实例
使用go声明启动一个goroutines:
go f(args)
Go运行时把goroutines放进OS线程里。
不要使用线程堵塞goroutines。
Channels被定义是为了与goroutines之间通信。
c := make(chan string) // goroutine 1 c <- "hello!" // goroutine 2 s := <-c fmt.Println(s) // "hello!"
select声明一个语句块来判断执行。
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毫秒。
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)) } }
func main() { start := time.Now() results := Google("golang") elapsed := time.Since(start) fmt.Println(results) fmt.Println(elapsed) }
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 }
同时执行 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 }
等待慢的服务器。
没有锁,没有条件变量,没有返回值。
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 }
func main() { start := time.Now() result := First("golang", fakeSearch("replica 1"), fakeSearch("replica 2")) elapsed := time.Since(start) fmt.Println(result) fmt.Println(elapsed) }
使用复制的服务以减少多余延迟。
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 基于你的工作空间更新导入声明
大部分人可以安全的使用这些工具。
play.golang.org/p/GPqra77cBK
The go tool 可以在一个传统目录布局中用源代码构建 Go 程序。不需要 Makefiles 或者其他配置。
匹配这些工具及其依赖,然后进行构建,安装:
% go get golang.org/x/tools/cmd/present
运行:
% present
为世界上所有的开源 Go 代码生成文档:
godoc.org
Eclipse, IntelliJ, emacs, vim 等等:
但是没有 “Go IDE”.
Go 工具无处不在。
Go 路线在线查看
tour.golang.org
大量的学习资料
golang.org/wiki/Learn
完美的社区
golang.org/project
Sameer Ajmani
Tech Lead Manager, Go team
@Sajma
sameer@golang.org