示例
Java程序猿对Go应该很眼熟
Main.java
| 1 2 3 4 5 | 
publicclassMain {
     publicstaticvoidmain(String[] args) {
         System.out.println("Hello, world!");
     }
 }
 | 
 
 
hello.go
| 1 2 3 4 5 | 
package main
 import "fmt"
 func main() {
     fmt.Println("Hello, 世界!")
 }
 | 
 
 
Hello, web server(你好,web服务)
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, 世界!")
 }
 | 
 
 
(访问权限)类型根据变量名来声明。
公共变量名首字大写,私有变量首字母小写。
示例:Google搜索前端
| 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))
 }
 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)
     
     query := req.FormValue("q")
     ifquery == ""{
         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 | 
 
     start := time.Now()
     results, err := Search(query)
     elapsed := time.Since(start)
     iferr != 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 
 }
 | 
 
 
Error类型可能包含额外的信息,可通过断言访问。
渲染搜索结果
| 1 2 3 4 5 6 7 8 9 10 11 12 | 
    type templateData struct{
         Results []Result
         Elapsed time.Duration
     }
     iferr := 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接口。
Go变量操作HTML模板
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 
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>
 ))
 | 
 
 
请求Google搜索API
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 
func Search(query string) ([]Result, error) {
     
     u, err := url.Parse("https://ajax.googleapis.com/ajax/services/search/web?v=1.0")
     iferr != nil {
         returnnil, err
     }
     q := u.Query()
     q.Set("q", query)
     u.RawQuery = q.Encode()
     
     resp, err := http.Get(u.String())
     iferr != nil {
         returnnil, err
     }
     defer resp.Body.Close()
 | 
 
 
defer声明使resp.Body.Close运行在Search方法返回时。
解析返回的JSON数据到Go struct类型
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
             }
         }
     }
     iferr := json.NewDecoder(resp.Body).Decode(&jsonResponse); err != nil {
         returnnil, err
     }
     
     var results []Result
     for_, r := range jsonResponse.ResponseData.Results {
         results = append(results, Result{Title: r.TitleNoFormatting, URL: r.URL})
     }
     returnresults, nil
 }
 | 
 
 
这就是它的前端
所有引用的包都来自标准库:
| 1 2 3 4 5 6 7 8 9 | 
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运行时把goroutines放进OS线程里。
不要使用线程堵塞goroutines。
Channels
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
select声明一个语句块来判断执行。
| 1 2 3 4 5 6 | 
select {
 casen := <-in:
   fmt.Println("received", n)
 caseout <- 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毫秒。
| 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 {
     returnfunc(query string) Result {
         time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
         returnResult(fmt.Sprintf("%s result for %q\n", kind, query))
     }
 }
 | 
 
 
Google搜索: 测试框架
| 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函数获取一个查询,然后返回一个的结果集 (不一定是字符串).
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
 }
 | 
 
 
Google搜索(并行)
同时执行 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) }()
     fori := 0; i < 3; i++ {
         result := <-c
         results = append(results, result)
     }
     return
 }
 | 
 
 
Google搜索 (超时)
等待慢的服务器。
没有锁,没有条件变量,没有返回值。
| 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)
     fori := 0; i < 3; i++ {
         select {
         caseresult := <-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) }
     fori := range replicas {
         go searchReplica(i)
     }
     return<-c
 }
 | 
 
 
使用First函数
| 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)
 }
 | 
 
 
Google搜索 (复制)
使用复制的服务以减少多余延迟。
| 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)
     fori := 0; i < 3; i++ {
         select {
         caseresult := <-c:
             results = append(results, result)
         case<-timeout:
             fmt.Println("timed out")
             return
         }
     }
     return
 |