golang基礎知識

Go基礎

第一個go程序(hello go!)

package main
import "fmt"
func main(){
  fmt.Println("hello Go Go Go !!")
}
複製代碼

要運行這個程序,將代碼保存爲xxx.go,而後使用go run main.gojava

包名必定要是main,至關於go程序的入口git

有時候咱們想讓程序編譯成二進制文件,可使用go build,而後就能夠直接運行了。正則表達式

go install這個命令會把go build編譯好的二進制包放到bin文件下算法

Go數據類型

  • int8 int16 int32 int64 float32 float64 bool string(string在go中算值類型了)array(數組在go中也是值類型)

java數據類型 byte short int lang float double booleanshell

package main
import "fmt"
func main(){
  //string可使用+鏈接在一塊兒
  fmt.Println("go"+"lang")
  fmt.Println("1+1=",1+1)
  fmt.Pritnln("7.0/3.0=",7.0/3.0)
  fmt.Println(true&&false)
  fmt.Println(true||false)
  fmt.Pritnln(!true)
}
複製代碼

變量

package main
import "fmt"
func main() {
	//聲明一個或多個變量
	//快速寫法
	var a string = "initial"
	fmt.Println(a)
	//正常寫法
	var dhy string
	dhy = "dhy"
	fmt.Println(dhy)
	//簡寫
	aaa := "aaa"
	fmt.Println(aaa)
	var b, c int = 1, 2
	fmt.Println(b, c)
	//Go將推斷變量的初始化類型
	var d = true
	fmt.Println(d)
	//聲明沒有初始值的變量將被初始化爲零值,如int的零值是0
	var e int
	fmt.Println(e)
	//var f string = "short"的簡化寫法
	f := "short"
	fmt.Println(f)
}
複製代碼

常量

Go支持的常量有字符、字符串、布爾值以及數值編程

package main
import "fmt"
import "math"
//常量不能夠用簡寫 即:const s:= "lll"
const s string = "constant"
func main(){
  fmt.Println(s)
  //const聲明能夠出如今任何var聲明出現的地方
  const n = 50000000
  //常量表達式能夠任意精度進行運算
  const d = 3e20 / n
  fmt.Println(d)
  //數值常量沒有類型,除非進行了顯式轉換等類型賦予
  fmt.Println(int64(d))
  //數值在使用環境上下文中會獲得類型,如變量賦值或者方法調用,如math.Sin須要的是一個float64
  fmt.Println(math.Sin(n))
}
複製代碼

For 循環結構

for是Go中惟一的循環結構,下面是三種基本的for循環類型。json

package main
import "fmt"
func main(){
  i := 1
  //最基本的類型,只有一個條件
  for i<=3{
    fmt.Println(i)
    i = i+1
  }
  //經典的 初始化/條件/循環後 for循環
  for j:=1;j<=9;j++{
    fmt.Println(j)
  }
  //沒有條件的for語句將無限循環,除非在內部的方法中使用了break或者return
  for{
    fmt.Println("loop")
    break
  }
  //也可使用continue來直接到下一個循環
  for n:=0;n<=5;n++ {
    if n%2 == 0{
      continue
    }
    fmt.Println(n)
  }
}
複製代碼

If/Else 條件語句

package main
import "fmt"
func main(){
  if 7%2==0{
    fmt.Println("7 is even")
  }else{
    fmt.Println("7 is odd")
  }
  //能夠單獨使用if
  if 8%4==0{
    fmt.Println("8 is divisible by 4")
  }
  //條件以前能夠有語句,聲明在該語句中的任何變量能夠用於全部分支
  if num:=9;num<0{
    fmt.Println(num,"is negative")
  }else if num<10{
    fmt.Println(num,"has 1 digit")
  }else{
    fmt.Println(num,"has multiple digits")
  }
}
複製代碼

注意,Go中條件周圍不須要圓括號,可是花括號是必要的。數組

Go中沒有三目if語句,因此對於最基本的條件也須要寫完整的if語句。安全

Switch 條件語句

package main
import "fmt"
import "time"
func main(){
  i:=2
  fmt.Print("write",i," as ")
  switch i{
  case 1: fmt.Println("one")
  case 2: fmt.Println("two")
  case 3: fmt.Println("three")
  }
  //能夠在同一個case語句中使用逗號來分隔多個表達式
  switch time.Now().Weekday(){
  case time.Saturday,time.Sunday:
    fmt.Println("it's the weekend")
  //這裏也使用了default case
  default:
    fmt.Println("it's a weekday")
  }
  t:=time.Now()
  //沒有表達式的switch是另外一種實現if/else邏輯的路子,同時這也展現了case表達式能夠是很是量值。
  switch{
  case t.Hour()<12:
    fmt.Println("it's before noon")
  default:
    fmt.Println("it's after noon")
  }
  //類型switch比較了類型而非值
 whatAmI := func(i interface{}) {
     //在switch中使用 變量名.(type) 查詢變量是由哪一個類型數據賦值
		switch t := i.(type) {
		case bool:
			fmt.Println("I'm a bool")
		case int:
			fmt.Println("I'm an int")
		default:
			fmt.Printf("Don't know type %T\n", t)
		}
	}
	whatAmI(true)
	whatAmI(1)
	whatAmI("hey")
}
複製代碼

Arrays 數組

數組跟切片基本徹底同樣,指定長度就是數組 ,不指定就是切片切片就是java中的ArrayListbash

在Go中,數組是特定長度元素的編號序列。

package main
import "fmt"
func main(){
  //這裏建立了一個5個int元素的數組。默認是零值,對於int而言就是0
  var a [5]int
  fmt.Println("emp:",a)
  //能夠經過array[index]=value語法設值,或者經過array[index]取值
  a[4]=100
  fmt.Println("set:",a)
  fmt.Println("get:",a[4])
  //內置的len函數返回數組的長度
  fmt.Println("len:",len(a))
  //聲明並初始化數組
  b:=[5]int{1,2,3,4,5}
  fmt.Prinln("dc1:",b)
  //數組是一維的,但你能夠組合類型來構建多維數組結構
  var twoD [2][3]int
  for i:=0;i<2;i++{
    for j:=0;j<3;j++{
      twoD[i][j]=i+j
    }
  }
  fmt.Println("2d: ",twoD)
}
複製代碼

當使用fmt.Println方法打印時,數組將以[v1 v2 v3 ... ]的形式展示

Slices 切片

slice是一個重要的數據類型,對於序列提供了比數組更強大的接口

package main
import "fmt"
func main(){
  //與數組不一樣,切片僅由其包含的元素(不是元素的數量)鍵入。 
  //要建立一個非零長度的空切片,請使用內置的make。 這裏咱們製做長度爲3的字符串(最初爲零值)。
  s:=make([]string,3)
  fmt.Println("emp:",s)
  //能夠像數組同樣set和get
  s[0]="a"
  s[1]="b"
  s[2]="c"
  fmt.Println("set:",s)
  fmt.Println("get:",s[2])
  //len可以返回slice的長度
  fmt.Println("len:",len(s))
  //做爲這些基本操做的補充,slice支持一些令其比數組更豐富的東西。
  //其中一個是append函數,其返回一個包含一個或多個新值的slice
  //注意須要接受append的返回值來獲取新的slice值
  s=append(s,"d")
  s=append(s,"e","f")
  fmt.Println("apd:",s)
  //slice能夠複製
  c:=make([]string,len(s))
  copy(c,s)
  fmt.Println("cpy:",c)
  //slice支持切片操做,語法爲slice[low:high]。以下將獲得元素s[2],s[3]和s[4]
  l:=s[2:5]
  fmt.Println("sl1:",l)
  //以下切片截止到(不包含)s[5]
  l=s[:5]
  fmt.Pritnln("sl2:",l)
  //以下切片從s[2]開始(包含)
  l=s[2:]
  fmt.Println("sl3:",l)
  //聲明並初始化slice
  t:=[]string{"g","h","i"}
  fmt.Println("dcl:",t)
  //slice也能夠組織成一個多爲數據結構,內部的slice長度能夠變化,這與多維數組不一樣
  twoD:=make([][]int,3)
  for i:=0;i<3;i++{
    innerLen:=i+1
    twoD[i]=make([]int,innerLen)
    for j:=0;j<innerLen;j++{
      twoD[i][j]=i+j
    }
  }
  fmt.Println("2d: ",twoD)
}
複製代碼

雖然slice與array是不一樣的類型,可是使用fmt.Println的展現結果很類似

Maps 鍵值對

Map是Go內置的關聯數據類型(其餘語言可能成爲哈希或者字典)

package main
import "fmt"
func main(){
  //要建立一個空的map,使用內置make函數:make(map[key-type]val-type)
  m:=make(map[string]int)
  //經過經典的name[key]=val語法來設置key/value對
  m["k1"]=7
  m["k2"]=13
  fmt.Println("map:",m)
  //經過name[key]來獲取一個value
  v1:=m["k1"]
  fmt.Println("v1: ",v1)
  //len函數返回map中鍵值對的個數
  fmt.Println("len:",len(m))
  //內置的delete函數將移除map中的鍵值對
  delete(m,"k2")
  fmt.Println("map:",m)
  //第一個值是該key的value,但此處不須要,故使用空白佔位符「_」忽略
  //第二個可選的返回值代表該鍵是否在map中,這樣能夠消除不存在的鍵,和鍵值爲0或者""的歧義
  _,prs:=m["k2"]
  fmt.Println("prs:",prs)
  //聲明並初始化map
  n:=map[string]int{"foo":1,"bar":2}
  fmt.Println("map:",n)
}
複製代碼

map將以[ k:v k:v ]的形式打印

Range 配合for一同使用

range能夠遍歷各類數據結構中的元素。

package main
import "fmt"
func main(){
  //這裏使用range來計算元素的和,數組也是相似的用法
  nums:=[]int{2,3,4}
  sum:=0
  for _,num:=range nums{
    sum+=num
  }
  fmt.Println("sum:",sum)
  //在slice和array上的range均爲每一個條目提供了索引和值
  for i,num:=range nums{
    if num==3{
      fmt.Pritnln("index",i)
    }
  }
  //map上的range經過key/value對進行遍歷
  kvs:=map[string]string{"a":"apple","b":"banana"}
  for k,v:=range kvs{
    fmt.Printf("%s -> %s\n",k,v)
  }
  //range能夠僅經過key進行遍歷
  for k:= range kvs{
    fmt.Println("key:",k)
  }
  //range做用在string上將獲得unicode code points,第一個值是字符的起始字節索引,第二個值是字符自己
  for i,c:range "go"{
    fmt.Println(i,c)
  }
}
複製代碼

函數

package main
import "fmt"

func plus(a int,b int) int{
  //Go須要顯式的return語句,它不會自動返回最後一個表達式的值
  return a+b
}
//a,b,b int 等同於 a int ,b int , c int
func plusPlus(a,b,c int) int{
  return a+b+c
}
func main(){
  //調用函數
  res := plus(1,2)
  fmt.Println("1+2=",res)
  res = plusPlus(1,2,3)
  fmt.Println("1+2+3=",res)
}
複製代碼

多返回值,這是一個很是好用的特性

Go內置支持了返回多個值。這一特色常常用於Go的習慣用法,例如同時返回結果和錯誤值

package main
import "fmt"
//方法簽名中的(int,int)代表它將返回兩個int值
func vals()(int,int){
  return 3,7
}
func main(){
  //這裏咱們經過多重賦值來使用兩個不一樣的返回值
  a,b := vals()
  fmt.Println(a)
  fmt.Println(b)
  //若是隻須要返回結果的子集,使用空白佔位符_
  _,c := vals()
  fmt.Println(c)
}
複製代碼

變參函數

變參函數,可使用任意數量的參數來進行調用。例如fmt.Println是一個常見的變參函數。

package main
import "fmt"
//這是一個接收任意數量的int值的函數
func sum(nums ...int){
  fmt.Print(nums," ")
  total := 0
  for _, num := range nums{
    total += num
  }
  fmt.Println(total)
}
func main(){
  sum(1,2)
  sum(1,2,3)
  //若是你已經在一個slice中定義了多個參數,可使用func(slice...)來直接應用到變參函數中
  nums := []int{1,2,3,4}
  sum(nums...)
}
複製代碼

匿名函數

Go支持匿名函數,它能夠造成閉包。當你想要定義一個不記名的內部函數時,匿名函數就頗有用了。

package main
import "fmt"
//intSeq函數返回另外一個函數,它定義在了intSeq函數內部,而且是匿名的。
//返回的函數關閉變量i以造成閉包
func intSeq() func int{
  i := 0
  return func() int{
    i+=1
    return i
  }
}
func main(){
  //調用intSeq,並將結果(一個函數)賦予nextInt
  //這個函數持有一個本身的i值,每次調用nextInt時都會更新
  nextInt:=intSeq()
  //屢次調用nextInt函數能夠看到閉包的效果
  //zephyr:如非閉包寫法,每次函數都會進行初始化變量i,反覆調用intSeq不會有這個效果
  fmt.Println(nextInt())
  fmt.Println(nextInt())
  fmt.Pritnln(nextInt())
  //要確認該狀態對該特定函數是惟一的,請建立並測試一個新函數。
  newInts:=intSeq()
  fmt.Println(newInts())
}
複製代碼

遞歸函數

Go支持遞歸函數。下面是一個經典的斐波那契數列示例

package main
import "fmt"
func fact(n int) int {
  if n==0 {
    return 1
  }
  //fact函數調用自身,直到n爲0
  return n * fact(n-1)
}
func main(){
  fmt.Println(fact(7))
}
複製代碼

指針

Go可使用指針,讓你在程序中傳遞值或記錄的引用。

package main
import "fmt"
//zeroval將取得ival的值的拷貝,與調用方不一樣
func zeroval(ival int){
  ival = 0
}
//zeroptr有一個*int類型的參數,表明它接收的是一個指針
func zeroptr(iptr *int){
  //*iptr解引用,從內存指定地址中獲取存放的值
  //對解引用指針的賦值將改變指定地址上的值
  *iptr = 0
}
func main(){
  i:=1
  fmt.Println("initial:",i)
  zeroval(i)
  fmt.Println("zeroval:",i)
  //&i 語法將得到變量i的內存地址,也就是指向變量i的指針
  zeroptr(&i)
  fmt.Println("zeroptr:",i)
  //指針也能夠被打印
  fmt.Println("pointer:",&i)
}
複製代碼

zeroval沒有改變main函數中i的值,而zeroptr會,由於它擁有指向變量i的內存地址。

結構體

Go的Struct結構是字段類型的集合。對於從記錄中將數據組織到一塊兒頗有幫助。

package main
import "fmt"
type person struct{
  name string
  age int
}
func main(){
  //這個語法建立了一個新的struct
  fmt.Println(person{"Bob",20})
  //在初始化struct時,能夠指定字段名
  fmt.Println(person{name:"Alice",age:30})
  //被忽略的字段將會被初始化爲零
  fmt.Println(person{name:"Fred"})
  //一個&將產生struct的指針
  fmt.Println(&person{name:"Ann",age:40})
  s:=person{name:"Sean",age:50}
  //經過點號訪問結構體中的字段
  fmt.Println(s.name)
  sp:=&s
  fmt.Println(sp.age)
  //對於結構體的指針也可使用點號操做符,指針將會自動解引用
  sp.age=51
  //結構體是可變的
  fmt.Println(sp.age)
}
複製代碼

方法,實際就是結構體的函數

Go支持在結構體上定義方法。

package main
import "fmt"
type rect struct{
  width,height int
}
//area方法有一個rect指針類型的接收器
func (r *rect) area() int{
  return r.width * r.height
}
//便可以定義指針類型的接收器,也能夠定義值類型的接收器
func (r rect) perim() int{
  return 2*r.width+2*r.height
}
func main(){
  r:=rect{width:10,height:5}
  //調用定義的方法
  fmt.Println("area: ",r.area())
  fmt.Println("perim: ",r.perim())
  //Go爲方法調用自動處理了值和引用的轉換。使用指針接收器能夠避免得到方法調用的拷貝(?)或容許方法修改接收到的struct值
  rp:=&r
  fmt.Println("area: ",rp.area())
  fmt.Println("perim: ",rp.perim())
}

複製代碼

接口

接口是方法簽名的命名集合。

package main
import "fmt"
import "math"

type geometry interface{
  area() float64
  perim() float64
}
type rect struct{
  width,height float64
}
type circle struct{
  radius float64
}
//在Go中,實現一個接口只須要實現其中定義的全部方法便可
func (r rect) area() float64{
  return r.width * r.height
}
func (r rect) perim() float64{
  return 2*r.width+2*r.height
}
func (c circle) area() float64{
  return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64{
  return 2 * math.Pi * c.radius
}
//若是變量是接口類型,那麼它能夠調用接口內定義的方法
//這是一個通用的measure方法,利用它可以工做在任何geometry上
func measure(g geometry){
  fmt.Println(g)
  fmt.Println(g.area())
  fmt.Println(g.perim())
}
func main(){
  r:=rect{width:3,height:4}
  c:=circle{radius:5}
  //rect和circle均實現了geometry接口,因此能夠做爲measure的參數
  measure(r)
  measure(c)
}

複製代碼

Errors 錯誤

在Go中,傳遞錯誤的慣用法是經過明確的,分離的返回值。這和Java活Ruby中的exception以及C中重載使用單個的結果/錯誤值不一樣。Go的方法使得很容易看出哪些函數返回錯誤,並使用與任何其餘非錯誤任務相同的語言結構來處理它們。

package main
import "errors"
import "fmt"
//按照慣例,錯誤是最後一個返回值,類型爲error,一個內置的接口。
func f1(arg int)(int,error){
  if arg==42{
    //errors.New使用給定的錯誤信息構建了一個基本的error值
    return -1,errors.New("can't work with 42")
  }
  //在error位置上放nil值表明沒有錯誤
  return arg+3,nil
}
//經過實現Error()方法,能夠自定義錯誤類型。這裏有一個上例的變種
//使用一個自定義類型來顯式地展現一個參數錯誤
type argError struct{
  arg int
  prob string
}
type argError struct{
  arg int
  prob string
}
func (e *argError) Error() string{
  return fmt.Sprintf("%d - %s",e.arg,e.prob)
}
func f2(arg int)(int,error){
  if arg==42{
    //這裏咱們使用&argError語法來建立一個新的結構體,提供了arg和prob兩個域的值
    return -1,&argError{arg,"can't work with it"}
  }
  return arg+3,nil
}
func main(){
  //下面的兩個循環測試了每一個返回error的函數
  //注意在if中的錯誤檢查是Go代碼中的習慣用法
  for _,i:=range []int{7,42}{
    if r,e:=f1(i);e!=nil{
      fmt.Println("f1 failed:",e)
    }else{
      fmt.Println("f1 worked:",r)
    }
  }
  for _,i := range []int{7,42}{
    if r,e :=f2(i);e!=nil{
      fmt.Println("f1 failed:",e)
    }else{
      fmt.Println("f1 worked:",r)
    }
  }
  //若是要以編程方式使用自定義錯誤中的數據,
  //則須要經過類型斷言將錯誤做爲自定義錯誤類型的實例獲取。
  _,e:=f2(42)
  if ae,ok := e.(*argError);ok{
    fmt.Println(ae.arg)
    fmt.Println(ae.prob)
  }
}
複製代碼

Goroutines 協程

goroutine是一個輕量級的執行線程。

package main
import "fmt"
func f(from string){
  for i:=0;i<3;i++{
    fmt.Println(from,";",i)
  }
}
func main(){
  //假設咱們有個f(s)的函數調用。這裏咱們經過通常方法調用,令其同步執行
  f("direct")
  //要想讓這個函數在goroutine中觸發,使用go f(s)。這個新的goroutine將會與調用它的並行執行
  go f("goroutine")
  //咱們也能夠啓動一個調用匿名函數的goroutine
  go func(msg string){
	fmt.Println(msg)
  }("going")
  //如今,這兩個方法調用在獨立的goroutine中異步執行了,故方法執行直接落到了這裏
  //Scanln代碼須要在程序退出前按下一個鍵
  var input string
  fmt.Scanln(&input)
  fmt.Println("done")
}

複製代碼

當咱們運行這個程序的時候,咱們將首先看到阻塞調用,而後是兩個goroutine的交錯輸出。這個交錯反應了goroutine在Go運行時是併發執行的。

Channels 通道,go鼓勵用這個進行同步

Channel是鏈接併發執行的goroutine的管道。你能夠從一個goroutine傳遞值到channel中,再在另外一個goroutine接收它。

package main
import "fmt"
func main(){
  //經過make(chan val-type)建立新的channel
  //channel的類型依賴於它們要傳遞的值
  messages:=make(chan string)
  //向channel傳遞值使用channel <- 語法。在這裏咱們從一個新的goroutine中發送了一個"ping"到message通道中
  go func(){messages<-"ping"}()
  //<-channel語法從channel中獲取值。這裏咱們接收了上面發送的"ping"信息並打印
  msg:=<-messages
  fmt.Println(msg)
}

複製代碼

當咱們運行這個程序時,"ping"信息成功的經過咱們的channel從一個goroutine傳遞到了另外一個。

默認狀況下,發送和接收在發送者和接受者都準備好以前阻塞。這個特性容許咱們在程序結尾等待"ping"信息而無需使用其餘的同步手段

緩衝通道

默認下channel沒有緩衝區,這意味着他們將只有在響應的接收者(<-chan)準備好時,才能容許發送(chan<-)。具備緩衝區的channel,接受有限個數的值,而無需相應的接收者。

package main
import "fmt"
func main(){
  //這裏咱們建立了一個可以緩衝2個字符串值的channel
  messages:=make(chan string,2)
  //因爲channel帶有緩衝區,咱們能夠發送值,無需響應的併發接收
  messages<-"buffered"
  messages<-"channel"
  //稍後,咱們像往常同樣,接收了這兩個值
  fmt.Println(<-messages)
  fmt.Println(<-messages)
}

複製代碼

Channel Synchronization

咱們可使用channel來跨goroutine同步執行。這裏是一個使用阻塞接收來等待goroutine結束的例子。

package main
import "fmt"
import "time"
//這個方法將在一個goroutine中運行。
//done channel用來通知其餘的goroutine這個方法執行完畢
func worker(done chan bool){
  fmt.Print("working...")
  time.Sleep(time.Second)
  fmt.Println("done")
  //發送一個值來通知這裏已經作完
  done<-true
}
func main(){
  啓動一個worker goroutine,賦予它用以通知的channel
  done:=make(chan bool,1)
  go worker(done)
  //在channel接收到來自worker的通知前,保持阻塞
  <-done
}

複製代碼

若是你移除了<-done行,這個程序可能會在worker開始前就結束。

Channel Directions 單向通道

當把channel用做函數的參數時,你能夠指定一個channel是否只發送或者只接收數據。這種特異性增長了程序的類型安全性。

package main
import "fmt"
//ping函數只接受一個發送數據的channel,若是試圖在其上獲取數據,將會引起編譯時異常
func ping(pings chan<- string,msg string){
  pings<-msg
}
//pong函數接受一個通道用於接收(pings),另外一個用於發送(pongs)
func pong(pings <-chan string,pongs chan<- string){
  msg:=<-pings
  pongs<-msg
}
func main(){
  pings:=make(chan string,1)
  pongs:=make(chan string,1)
  ping(pings,"passed message")
  pong(pings,pongs)
  fmt.Println(<-pongs)
}

複製代碼

Select 處理多個協程

Go的select讓你可以等待多個channel操做。經過select結合goroutine和channel是Go的重要特點。

package main
import "time"
import "fmt"
func main(){
  //本例中咱們將在兩個通道中進行選擇
  c1:=make(chan string)
  c2:=make(chan string)
  //每一個通道都會在必定時間後接收到一個值,在併發的goroutine中模擬阻塞RPC操做執行
  go func(){
    time.Sleep(time.Second*1)
    c1<-"one"
  }()
  go func(){
    time.Sleep(time.Second*2)
    c2<-"two"
  }()
  //咱們將使用select來同時等待這兩個值,當它們到達時打印。
  for i:=0;i<2;i++{
    select{
    case msg1:=<-c1:
      fmt.Println("received",msg1)
    case msg2:=<-c2:
      fmt.Println("received",msg2)
    }
  }
}

複製代碼

按照預期,咱們將接收到"one",而後"two"。

注意,整個執行只須要大約2秒的時間,由於1秒和2秒的沉睡是併發執行的。

Timeouts 超時

超時對於鏈接到外部資源的程序或其餘須要綁定執行時間的程序很重要。在Go中,能夠經過channel和select輕鬆而優雅的實現超時。

package main
import "time"
import "fmt"
func main(){
  //在本例中,假設咱們執行了一個外部調用,它在兩秒後將結果返回到通道c1上
  c1:=make(chan string,1)
  go func(){
    time.Sleep(time.Second*2)
    c1<-"result 1"
  }()
  //這裏是用select實現超時。res:=<-c1等待一個結果,而<-Time.After等待超時1秒後發送一個值。
  //因爲select將在有第一個準備就緒的接收時繼續,咱們會在操做超過容許的1秒時進入超時事件。
  select{
  case res:=<-c1:
    fmt.Println(res)
  case <-time.After(time.Second*1):
  	fmt.Println("timeout 1")  
  }
  //若是咱們容許一個更長的超時時間3秒,則將能成功獲得c2的值,並打印
  c2:=make(chan string,1)
  go func(){
    time.Sleep(time.Second*2)
    c2<-"result 2"
  }()
  select{
  case res:=<-c2:
    fmt.Println(res)
  case <-time.After(time.Second*3):
    fmt.Println("timeout 2")
  }
}

複製代碼

運行這個程序,將顯示第一個操做超時了,第二個則成功。

使用select超時模式須要在通道上進行結果通信。通常狀況下這是很好的主意,由於其餘的重要Go特性基於通道和選擇。咱們將在以後看到有關的兩個例子:timer和ticker

非阻塞通道

channel上簡單的發送和接收是阻塞的。然而,咱們可使用select和default子句來實現非阻塞發送、接收甚至非阻塞的多路選擇。

package main
import "fmt"
func main(){
  messages:=make(chan string)
  signals:=make(chan bool)
  //這是一個非阻塞的接收。若是message的值能夠獲取,select將隨值進入<-message子句
  //不然將馬上進入default事件
  select{
  case msg:=<-messages:
    fmt.Println("received message",msg)
  default:
    fmt.Pritln("no message received")
  }
  msg:="hi"
  //相似的有非阻塞發送
  select{
  case messages<-msg:
    cmt.Println("sent message",msg)
  default:
    fmt.Println("no message sent")
  }
  //咱們能夠在default上使用多個事件來實現多路非阻塞select。
  //這裏咱們試圖在message和signal上均進行非阻塞接收
  select{
  case msg:=<-messages:
    fmt.Println("received message",msg)
  case sig:=<-signals:
    fmt.Println("received signal",sig)
  default:
    fmt.Println("no activity")
  }
}

複製代碼

Closing Channels 通道關閉

關閉通道意味着不會再有值在其上發送。這對於通道的接收方通信完成頗有幫助

package main
import "fmt"
//在這個例子中,咱們將使用一個做業通道將工做從主函數goroutine傳遞給工人 goroutine。當沒有更多做業給工人時,咱們將關閉工做渠道。
func main(){
  jobs:=make(chan int,5)
  done:=make(chan bool)
  //下面是工人goroutine。它經過j,more:=<-jobs反覆獲取做業
  //在這個2返回值的接收中,若是做業關閉,全部值都已接收,more會變爲false
  //咱們用其在完成全部做業時進行已完成通知
  go func(){
    for{
      j,more:=<-jobs
      if more{
        fmt.Println("received job",j)
      }else{
        fmt.Println("received all jobs")
        done<-true
        return
      }
    }
  }()
  //此處向工人發送了3個做業,而後關閉它
  for j:=1;j<=3;j++{
    jobs<-j
    fmt.Println("sent job",j)
  }
  close(jobs)
  fmt.Println("sent all jobs")
  //使用前面見過的同步機制來等待工人、
  <-done
}

複製代碼

遍歷通道

在前面的例子中,咱們看到如何使用for和range來遍歷基本的數據結構。咱們一樣可使用這個語法來遍歷通道上接收的值。

package main
import "fmt"
func main(){
  //遍歷queue通道上的2個值
  queue:=make(chan string,2)
  queue<-"one"
  queue<-"two"
  close(queue)
  //range遍歷通道上接收的每一個值。因爲在上面關閉了通道,這個遍歷將在接收2個值後結束。
  for elem:=range queue{
    fmt.Println(elem)
  }
}
複製代碼

Timers

咱們常想在將來的某一刻執行Go代碼,或者在某一時間內重複執行。Go內置的timer和ticker使這些任務十分簡易。首先咱們看看timer,而後再看ticker。

package main
import "time"
import "fmt"
func main(){
  //timer表明將來的一個單獨事件。你要告訴它要等待多久,它提供一個通道,在指定時間發出通知。下面這個timer將等待2秒鐘
  timer1:=time.NewTimer(time.Second*2)
  //定時器通道因爲操做<-timer1.c發生阻塞,直到它發送了一個值來代表定時器到時
  <-timer1.C
  fmt.Println("Timer 1 expired")
  //若是你僅僅想等待一段時間,能夠用time.Sleep,使用timer的一個緣由是,你能夠在計時結束前取消,以下例:
  timer2:=time.NewTimer(time.Second)
  go func(){
    <-timer2.C
    fmt.Println("Timer 2 expired")
  }()
  stop2:=timer2.Stop()
  if stop2{
    fmt.Println("Timer 2 stopped")
  }
}
複製代碼

第一個timer將在啓動程序後大約2秒到時,但第二個應會在其有機會到時前先行中止。

Tickers

timer用來在未來的某一時間作某事一次。而ticker會在一個指定時間間隔重複作某事。這裏是一個ticker的例子,它會在咱們中止以前按期觸發。

package main
import "time"
import "fmt"
func main(){
  //ticker與timer的機制類似,都是一個發送值的通道
  //這裏咱們使用channel內置的range來遍歷每500ms到達的值
  ticker:=time.NewTicker(time.Millisecond*500)
  go func(){
    for t:=range ticker.C{
	  fmt.Prinltn("Tick at ",t)
    }
  }()
  //ticker能夠像timer同樣中止。一旦ticker中止,將不會在其通道上接收到任何信息。咱們將在1600ms後結束。
  time.Sleep(time.Millisecond*1600)
  ticker.Stop()
  fmt.Println("Ticker stopped")
}

複製代碼

運行這個程序,在結束以前,應該會tick三次。

Worker Pools

本例中咱們將看到如何使用goroutine和channel來實現一個工人池

package main
import "fmt"
import "time"
//這裏是咱們將要運行多個併發實例的工人。他們將從jobs通道獲取任務,並將相應的結果返回到results上。咱們將在每一個做業沉睡1秒,來模擬一個複雜的任務。
func worker(id int,jobs <-chan int,results chan<- int){
  for j:=range jobs{
    fmt.Println("worker",id,"started job",j)
    time.Sleep(time.Second)
    fmt.Println("worker",id,"finished job",j)
    results <- j*2
  }
}
func main(){
  //爲了使用工人池,咱們須要派發任務而且回收結果。咱們使用兩個通道來作這件事
  jobs:=make(chan int,100)
  results:=make(chan int,100)
  //這裏啓動了3個工人,初始時阻塞,由於當前沒有做業
  for w:=1;w<=3;w++{
    go worker(w,jobs,results)
  }
  //添加5個做業,而後關閉通道來代表這就是全部的工做
  for j:=1;j<=5;j++{
    jobs<-j
  }
  close(jobs)
  
  for a:=1;a<=5;a++{
    <-results
  }
}

複製代碼

運行的項目展現了有5個做業得以被不一樣的工人執行。儘管總共有5秒鐘的時間,這個程序只須要2秒鐘,由於有3名工做人員同時進行操做。

同時啓動了3個worker,來監聽通道是否有做業發出,無做業時worker不會進入循環體,爲空操做。從而造成工人池

通道速率控制

速率限制是控制資源利用和維護服務質量的重要機制。Go經過goroutine,channel和ticker能夠優雅的支持速率控制。

package main
import "time"
import "fmt"
func main(){
  //首先,咱們看下基本的速率控制。
  //假設咱們想要控制處理的輸入請求,咱們經過同一個通道來爲這些請求提供服務
  requests:=make(chan int,5)
  for i:=1;i<=5;i++{
    request<-i
  }
  close(requests)
  //limiter通道將每過200毫秒接收一次數據。這是速率控制策略中的調節器
  limiter:=time.Tick(time.Millisecond*200)
  //在服務每一個請求以前,經過limiter通道阻塞接收,咱們將本身限制在每200ms處理1個請求上。
  for req:=range request{
    <-limiter
    fmt.Println("request",req,time.now())
  }
  //咱們可能但願在咱們的速率限制方案中容許短期的請求,同時保留總體速率限制。 
  //咱們能夠經過緩衝限制器通道來實現這一點。
  //這個burstyLimiter通道將容許多達3個事件的突發。
  burstyLimiter:=make(chan time.Time,3)
  //填充通道,來展現可容許的突發
  for i:=0;i<3;i++{
    burstyLimiter<-time.Now()
  }
  //每過200毫秒將試圖添加一個新值到burstyLimiter,最多3個
  go func(){
    for t:=range time.Tick(time.Millisecond*200){
      burstyLimiter<-t
    }
  }()
  //模擬5個輸入請求。前3個將受益於burstyLimiter的突發能力
  burstyRequests:=make(chan int,5)
  for i:=1;i<=5;i++{
    burstyRequest<-i
  }
  close(burstyRequests)
  for req:=range burstyRequests{
    <-burstyLimiter
    fmt.Println("request",req,time.Now())
  }
}

複製代碼

運行咱們的程序,咱們看到第一批請求根據須要每200毫秒處理一次。

對於第二批請求,因爲可突發速率限制,咱們當即爲前3個服務,而後以約200ms的延遲提供剩餘的2個。

Atomic Counters

Go中管理狀態的主要機制是經過渠道進行溝通。 咱們以工人池爲例。 還有一些管理狀態的其餘選項。 這裏咱們來看一下使用sync / atomic包來進行多個goroutines訪問的原子計數器。

package main
import "fmt"
import "time"
import "sync/atomic"

func main(){
  //咱們使用無符號整數來表明咱們的計數器
  var ops unit64=0
  //爲了模擬併發更新操做,咱們將啓動50個goroutine,每一個都會在每過1毫秒時增加一次計數器
  for i:=0;i<50;i++{
    go func(){
      for{
        //爲了原子地增長計數器,咱們使用AddUint64,使用&語法給它咱們的ops計數器的內存地址。
        atomic.AddUnit64(&ops,1)
        //在兩個增加之間稍做等待
        time.Sleep(time.Millisecond)
      }
    }()
  }
  //等一下讓一些操做積累起來。
  time.Sleep(time.Second)
  //爲了安全地使用計數器,當它仍被其餘goroutine更新時,咱們經過LoadUint64將當前值的副本提取到opsFinal中。
  //如上所述,咱們須要給出這個函數來獲取值的內存地址和操做。
  opsFinal:=atomic.LoadUnit64(&ops)
  fmt.Println("ops:",opsFinal)
}

複製代碼

運行程序能夠看到咱們執行了大約40,000次操做。

Mutexes

在前面的例子中咱們看到如何使用原子操做管理簡單的計數器狀態。爲了處理更復雜的狀態,咱們可使用一個mutext來安全的訪問不一樣gorotine之間的數據。

package main
import(
  "fmt"
  "math/rand"
  "sync"
  "sync/atomic"
  "time"
)
func main(){
  //這個例子中,狀態是一個map
  var state=make(map[int]int)
  //這個mutext將同步訪問狀態值
  var mutex=&sync.Mutex{}
  //咱們將記錄作了多少讀寫操做
  var readOps unit64=0
  var writeOps unit64=0
  //這裏咱們啓動了100個goroutine來重複讀取狀態值
  //每毫秒在每一個goroutine執行一次
  for r:=0;r<100;r++{
    go func(){
      total:=0
      for{
        //每次讀取時咱們將得到一個進入的鑰匙
        //Lock()住mutex來保證獨家訪問狀態值
        //在選中的鑰匙上讀取值,Unlock()掉mutext
        //對readOps計數加1
        key:=rand.Intn(5)
        mutex.Lock()
        total+=state[key]
        mutex.Unlock()
        atomic.AddUnit64(&readOps,1)
        //每次操做等待一下
        time.Sleep(time.Millisecond)
      }
    }()
  }
  //再啓動10個模擬寫的goroutine,與讀取相似的模式
  for w:=0;w<10;w++{
    go func(){
      for{
        key:=rand.Intn(5)
        val:=rand.Intn(100)
        mutex.Lock()
        state[key]=val
        mutex.Unlock()
        atomic.AddUnit64(&writeOps,1)
        time.Sleep(time.Millisecond)
      }
    }()
  }
  //讓這10個go協程在state和mutext上工做一下子
  time.Sleep(time.Second)
  //獲取而且報告最終操做的個數。
  readOpsFinal:=atomic.LoadUnit64(&readOps)
  fmt.Println("readOps:",readOpsFinal)
  writeOpsFinal:=atomic.LoadUnit64(&writeOps)
  fmt.Println("writeOps:",writeOpsFinal)
  //隨着最後一次鎖住狀態,展現它是如何結束的
  mutex.Lock()
  fmt.Println("state:",state)
  mutex.Unlock()
}
複製代碼

運行程序能夠看到咱們在mutex同步狀態上執行了將近90,000操做。

Stateful Goroutines

在上個例子咱們使用mutex顯式鎖定多個goroutine要同步訪問的共享狀態。另外一個選擇是使用goroutine和channel內置的同步功能來達到相同的結果。這種基於渠道的方法與Go經過通訊和擁有徹底一個goroutine的每一個數據來共享內存的想法相一致。

package main
import(
  "fmt"
  "math/rand"
  "sync/atomic"
  "time"
)
//在這個例子中,狀態值將被一個單獨的goroutine擁有
//這保證了數據不會受到併發訪問的影響
//爲了讀或寫狀態值,其它goroutine將向擁有它的goroutine發送一個消息,而後接收其回覆。
//readOp和writeOp結構封裝了這些請求和響應
type readOp struct{
  key int
  resp chan int
}
type writeOp struct{
  key int
  val int
  resp chan bool
}
func main() {
  //像以前同樣咱們記錄執行了多少次操做
  var readOp unit64=0
  var writeOps unit64=0
  //reads和writes通道將被用於其它goroutine分別發送讀寫請求
  reads:=make(chan *readOp)
  writes:=make(chan *writeOp)
  //這裏即是擁有狀態值的goroutine,與以前同樣是個map,但被私有化
  //這個goroutine反覆選擇reads和writes通道,響應到達的請求。
  //首先執行所請求的操做而後在響應通道上發送值來表示成功執行響應
  //(或者reads指望的數據)
  go func(){
    var state=make(map[int]int)
    for{
      select{
      case read:=<-reads:
        read.resp<-state[read.key]
      case write:=<-writes:
        state[write.key]=write.val
        write.resp<-true
      }
    }
  }()
  //啓動100個goroutine,經過讀取通道來讀取有狀態的goroutine
  //每次讀取須要構建一個readOp,經過reads發送給它
  //再經過所提供的resp通道獲取結果
  for r:=0;r<100;r++{
    go func(){
      for {
        read:=&readOp{
          key:rand.Intn(5),
          resp:make(chan int)
        }
        reads<-read
        <-read.resp
        atomic.AddUnit64(&readOps,1)
        time.Sleep(time.Millisecond)
      }
    }()
  }
  //啓動10個寫操做
  for w:=0;w<10;w++{
    go func(){
      for {
        write:=&writeOp{
          key:rand.Intn(5),
          val:rand.Intn(100),
          resp:make(chan bool)
        }
      }
    }()
  }
  time.Sleep(time.Second)
  readOpsFinal:=atomic.LoadUnit64(&readOps)
  fmt.Println("readOps:",readOpsFinal)
  writeOpsFinal:=atomic.LoadUnit64(&writeOps)
  fmt.Println("writeOps:",writeOpsFinal)
}

複製代碼

運行項目顯示基於gouroutine的狀態管理完成了大約80,000操做。

Sorting 排序

sort包實現了內置和自定義類型的排序。首先看看內置類型的排序。

package main
import "fmt"
import "sort"
func main(){
  //sort改變了給定的slice,而不是返回一個新的
  strs:=[]string{"c","a","b"}
  sort.Strings(strs)
  fmt.Println("Strings:",strs)
  ints:=[]int{7,2,4}
  sort.Ints(ints)
  fmt.Println("Ints:",ints)
  //可使用sort檢查一個slice是否是已經排好序了
  s:=sort.IntsAreSorted(ints)
  fmt.Println("Sorted:",s)
}

複製代碼

Sorting by Functions

有時候咱們想要對一個集合進行非天然順序的排序。例如,咱們想要把字符串根據長度而非字典順序排序,下面是一個定製排序的例子。

package main
import "fmt"
import "sort"
//爲了根據自定義函數排序,咱們須要相應的類型
//這裏咱們建立了一個ByLength類型
//這就是一個[]string類型的別名
type ByLength []string
//咱們在ByLength上實現了sort接口的Len、Less和Swap方法
//這裏咱們想要按照字符串長度的增序排列
func (s ByLength) Len() int{
  return len(s)
}
func (s ByLength) Swap(i,j int){
  s[i],s[j] = s[j],s[i]
}
func (s ByLength) Less(i,j int) bool {
  return len(s[i])<len(s[j])
}
//經過將原有的fruits片斷轉換爲ByLength
//就可使用sort.Sort來進行自定義排序了。
func main(){
  fruits:=[]string{"peach","banana","kiwi"}
  sort.Sort(ByLength(fruits))
  fmt.Println(fruits)
}

複製代碼

經過相似的模式建立自定義類型,實現三個接口方法,而後調用sort,咱們能夠對集合進行任意的排序。

Panic

panic一般指發生了不曾預料的錯誤。大多數狀況下,咱們使用它來將不該當在正常操做中發生的東西快速失敗,或者不許備妥善處理。

package main
import "os"
func main(){
  //咱們將在整個網站使用panic來檢查意外的錯誤。
  //這是該網站上惟一旨在panic的程序。
  panic("a problem")
  //panic的一個常見做用是終止一個函數返回了一個不知道如何處理或者不想處理的錯誤。
  //這裏是一個panic的例子,在建立一個新文件時發生意外錯誤
  _,err:=os.Create("/tmp/file")
  if err!=nil{
    panic(err)
  }
}
複製代碼

運行這個程序將引發一個panic,打印錯誤信息和goroutine蹤影,並以非0狀態退出。

注意,不像一些用異常來處理大多錯誤的語言,在Go的習慣用法中,儘量使用錯誤指示的返回值。

Defer

defer用於確保一個函數調用在程序執行中延遲做用,常常用於清理目的。defer經常使用語其餘語言的ensurefinnaly用的地方。

packgae main
import "fmt"
import "os"
//假設咱們想要建立並寫一個文件,在完成後關閉它。
func main(){
  //在獲取一個文件對象後當即使用defer並關閉這個文件
  //這個將在main函數末尾關閉的時候執行,在writeFile完成後
  f:=createFile("/tmp/defer.txt")
  defer closeFile(f)
  writeFile(f)
}
func createFile(p string) *os.File{
  fmt.Println("creating")
  f,err:=os.Create(p)
  if err!=nil{
    panic(err)
  }
  return f
}
func writeFile(f *os.File){
  fmt.Println("writing")
  fmt.Println(f,"data")
}
func closeFile(f *os.File){
  fmt.Println("closing")
  f.Close()
}
複製代碼

運行程序,確認這個文件的確在寫以後再關閉的。

Collection Functions

咱們常常須要咱們的程序對數據集合執行操做,例如選擇知足給定謂詞的全部項目或將全部項目映射到具備自定義函數的新集合。

一些語言一般的慣用法是使用泛型數據結構和算法。 Go不支持泛型; 在Go中,一般在程序和數據類型特別須要時提供集合功能。

如下是一些用於字符串切片的示例集合函數。 您可使用這些示例來構建本身的函數。 請注意,在某些狀況下,直接內聯集合操做代碼多是最爲清晰的,而不是建立和調用幫助函數。

package main
import "strings"
import "fmt"
// 返回目標字符串t的第一個索引,若是沒有找到則返回-1
func Index(vs []string,t string) int{
  for i,v:=range vs{
    if v==t{
      return i
    }
  }
  return -1
}
//若是字符串在切片中,則返回true
func Include(vs []string,t string) bool{
  return Index(vs,t)>=0
}
//若是有一個字符串知足指望的f則返回true
func Any(vs []string,f func(string) bool) bool{
  for _,v:=range vs{
    if f(v){
      return true
    }
  }
  return false
}
//全部的字符串知足指望的f則返回true
func All(vs []string,f func(string) bool) bool{
  for _,v:=range vs{
    if !f(v){
      return false
    }
  }
  return true
}
//返回一個知足給定方法的全部字符串
func Filter(vs []string,f func(string) bool) []string{
  vsf :=make([]string,0)
  for _,v:=range vs{
    if f(v){
      vsf=append(vsf,v)
    }
  }
  return vsf
}
//返回一個包含將f函數應用於原始切片中每一個字符串的結果的新切片。
func Map(vs []string,f func(string) string)[] string{
  vsm := make([]string,len(vs))
  for i,v:=range vs{
    vsm[i]=f(v)
  }
  return vsm
}
func main(){
  var strs=[]string{"peach","apple","pear","plum"}
  fmt.Println(Index(strs,"pear"))
  fmt.Println(Include(strs,"grape"))
  fmt.Println(Any(strs,func(v string) bool{
    return strings.HasPrefix(v,"p")
  }))
  fmt.Println(All(strs,func(v string) bool{
    return strings.HasPrefix(v,"p")
  }))
  fmt.Println(Filter(strs,func(v string) bool{
    return strings.Contains(v,"e")
  }))
  //以上示例所有使用匿名函數,但也可使用正確類型的命名函數。
  fmt.Println(Map(strs,strings.ToUpper))
}

複製代碼

String Functions

標準庫的String包提供了許多有用的字符串相關的函數。這裏有些示例讓你對這個包有個初步的認識。

package main
import s "strings"
import "fmt"
//鑑於後文大量用到,咱們給字符串輸出函數起一個別名
var p=fmt.Println
func main(){
  //這裏是字符串能夠用的函數示例
  //因爲這些函數是包中的而不是字符串對象自己的方法,咱們須要給他們傳遞一個字符串參數
  //你能夠在strings包下的doc找到更多的函數
  p("Contains: ",s.Contains("test","es"))
  p("Count: ",s.Count("test","t"))
  p("HasPrefix: ",s.HasPrefix("test","te"))
  p("HasSuffix: ",s.HasSuffix("test","st"))
  p("Index: ",s.Index("test","e"))
  p("Join: ",s.Join([]string{"a","b"},"-"))
  p("Repeat: ",s.Repeat("a",5))
  p("Replace: ",s.Replace("foo","o","0",-1))
  p("Replace ",s.Replace("foo","o","0",1))
  p("Split: ",s.Split("a-b-c-d-e","-"))
  p("ToLower: ",s.ToLower("TEST"))
  p("ToUpper: ",s.ToUpper("test"))
  p()
  //並不是strings包的一部分,可是這裏值得一提
  //即獲取字符串長度以及字符串中的某個字符
  p("Len: ",len("hello"))
  p("Char: ","hello"[1])
}

複製代碼

注意,獲取長度和索引字符是工做在字節級別上的。Go使用UTF-8編碼字符串。

String Formatting 格式化

Go在經典的printf上提供了優秀的字符串格式化支持。這裏有一些常見格式化的例子。

package main
import "fmt"
import "os"
type point struct{
  x,y int
}
func main(){
  //Go提供了一些修飾符來格式化通常的Go數值
  //例如,這裏輸出了一個point結構體的實例
  p:=point{1,2}
  fmt.Printf("%v\n",p)
  //若是值是結構體,%+v變量將輸出結構體中的域名稱
  fmt.Printf("%+v\n",p)
  //%#v將打印Go語法形式
  fmt.Printf("%#v\n",p)
  //要想打印值的類型,使用%T
  fmt.Printf("%T\n",p)
  //格式化輸出布爾值
  fmt.Printf("%t\n",true)
  //整數有許多格式化選項,使用%d來進行標準十進制格式化
  fmt.Printf("%d\n",123)
  //這樣打印了二進制形式
  fmt.Printf("%b\n",14)
  //這裏打印了十進制數字對應的字符
  fmt.Printf("%c\n",33)
  //%x提供了十六進制編碼
  fmt.Printf("%x\n",456)
  //浮點數也有許多格式化選項,標準十進制使用%f
  fmt.Printf("%f\n",78.9)
  //%e和%E格式化浮點數爲科學計數法
  fmt.Printf("%e\n",123400000.0)
  fmt.Printf("%E\n",123400000.0)
  //基本的字符串輸出,使用%s
  fmt.Pirntf("%s\n","\"string\"")
  //將Go代碼中的字符串加引號輸出(等同參數形式)
  fmt.Printf("%q\n","\"string\"")
  //將字符串十六進制編碼化
  fmt.Printf("%x\n","hex this")
  //打印一個指針
  fmt.Printf("%p\n",&p)
  //控制數字輸出的寬度和精度,在%後面跟上數字能夠控制寬度,.後面跟上數字控制精度
  //默認靠右顯示,用空格填充
  fmt.Printf("|%6d|%6d|\n",12,345)
  fmt.Printf("|%6.2f|%6.2f|\n",1.2,3.45)
  //靠左對齊使用-
  fmt.Printf("|%-6.2f|%-6.2f|\n",1.2,3.45)
  //你可能也須要控制字符串輸出的格式,特別是要以表格形式輸出的時候
  fmt.Printf("|%6s|%6s|\n","foo","b")
  fmt.Printf("|%-6s|%-6s|\n","foo","b")
  //Printf將格式化的字符串打印到標準輸出上
  //Sprintf格式化並返回這個字符串而不打印
  s:=fmt.Sprintf("a %s","string")
  fmt.Println(s)
  //你也能夠格式化並打印到io.Writers而非os.StdOut
  fmt.Fprintf(os.Stderr,"an %s\n","error")
}

複製代碼

正則表達式

Go爲正則表達式提供了內置的支持。這裏是一些經常使用的正則相關的任務。

package main
import "bytes"
import "fmt"
import "regexp"
func main(){
  //測試模式是否符合字符串
  math,_:=regexp.MatchString("p([a-z]+)ch","peach")
  fmt.Printf(match)
  //上面咱們直接使用了字符串模式。可是對於其餘正則任務,你須要先編譯一個正則表達式結構體
  r,_:=regexp.Compile("p([a-z]+)ch")
  //這種結構體上有許多方法。這裏是一個如同上面的匹配測試
  fmt.Println(r.MatchString("peach"))
  //這裏找到一個匹配
  fmt.Println(r.FindString("peach punch"))
  //這裏也是尋找第一個匹配,但返回起止的索引而非字符串
  fmt.Println(r.FindStringIndex("peach punch"))
  //submatch包含整串匹配,也包含內部的匹配。
  fmt.Println(r.FindStringSubmatch("peach punch"))
  //與上面相似,返回整串匹配和內部匹配的索引信息
  fmt.Println(r.FindStringSubmatchIndex("peach punch"))
  //這些函數的All修飾將返回輸入中全部匹配的,不只僅是第一個。例如找到全部匹配正則表達式的
  fmt.Println(r.FindAllString("peach punch pinch",-1))
  fmt.Println(r.FindAllStringSubmatchIndex("peach punch pinch",-1))
  //第二個參數若是是非負數,則將限制最多匹配的個數
  fmt.Println(r.FindAllString("peach punch pinch",2))
  //例子中都是字符串參數,而且用了相似於MatchString這樣的名字
  //咱們能夠提供[]byte參數,並將函數名中的String去掉
  fmt.Println(r.Match([]byte("peach")))
  //當用正則表達式建立一個常量的時候你可使用MustCompile。
  //一個純Compile不能用於常量,由於它有2個返回值。
  r=regexp.MustComplie("p([a-z]+)ch")
  fmt.Println(r)
  //regexp包也能用於使用其餘值替換字符串的子集
  fmt.Println(r.ReplaceAllString("a peach","<fruit>"))
  //Func修飾容許你使用一個給定的函數修改匹配的字符串
  in:=[]byte("a peach")
  out:=r.ReplaceAllFunc(in,bytes.ToUpper)
  fmt.Println(string(out))
}
複製代碼

JSON

Go內置提供了JSON的編碼和解碼是,包括內置和自定義的數據類型。

package main
import "encoding/json"
import "fmt"
import "os"
//咱們使用這兩個結構體來展現自定義類型的編碼和解碼
type Response1 struct{
  Page int
  Fruits []string
}
type Response2 struct{
  Page int `json:"page"`
  Fruits []string `json:"fruits"`
}
func main(){
  //首先咱們來看編碼基本數據類型到JSON字符串。
  //這裏有一些原子類型的示例
  bolB,_:=json.Marshal(true)
  fmt.Println(string(bolB))
  intB,_:=json.Marshal(1)
  fmt.Println(string(intB))
  fltB,_:=json.Marshal(2.34)
  fmt.Println(string(fltB))
  strB,_:=json.Marshal("gopher")
  fmt.Println(string(strB))
  //這裏有些slice和map的例子,他們將按需編碼成JSON數組和對象。
  slcD:=[]string{"apple","peach","pear"}
  slcB,_:=json.Marshal(slcD)
  fmt.Println(string(slcB))
  mapD:=map[string]int{"apple":5,"lettuce":7}
  mapB,_:=json.Marshal(mapD)
  fmt.Println(string(mapB))
  //JSON包將自動編碼你的自定義數據類型
  //他將在編碼後的輸出中只包含對外的域而且用名字做爲JSON鍵
  res1D:=&Response1{
    Page:1,
    Fruits:[]string{"apple","peach","pear"}
  }
  res1B,_:=json.Marshal(res1D)
  fmt.Println(string(res1B))
  //你能夠在結構體的域聲明上定製編碼後的JSON鍵名。
  //能夠看上面Reponse2的定義
  res2D:=&Reponse2{
    Page:1,
    Fruits:[]string{"apple","peach","pear"}
  }
  res2B,_:=json.Marshal(res2D)
  fmt.Println(string(res2B))
  //咱們須要提供一個變量來放JSON包編碼的值。
  //map[string]interface{}將承載一個字符串數據類型的map
  byt:=[]byte(`{"num":6.13,"strs":["a","b"]}`)
  var dat map[string]interface{}
  //這裏是相應的解碼,以及相關錯誤的檢查
  if err:=json.Unmarshal(byt,&dat);err!=nil{
    panic(err)
  }
  fmt.Println(dat)
  //爲了使用解碼的map的值,咱們須要將它們轉換爲合適的類型
  //例如這裏咱們將num中餓值轉變爲float64類型
  num:dat["num"].(float64)
  fmt.Println(num)
  //訪問內部數據須要一系列的轉換
  strs:=dat["strs"].([]interface{})
  str1:=strs[0].(string)
  fmt.Println(str1)
  //咱們也能夠將JSON解碼到定製的數據類型
  //這樣爲程序添加了額外的類型安全,並再也不須要在訪問數據的時候進行類型斷言
  str:=`{"page":1,"fruits":["apple","peach"]}`
  res:=Response2{}
  json.Unmarshal([]byte(str),&res)
  fmt.Println(res)
  fmt.Println(res.Fruits[0])
  //在上面的例子中咱們常用bytes和字符串在標準輸出上來進行數據和JSON形式的交互
  //咱們也能夠將JSON編碼流入到os.Writers甚至HTTP響應體
  enc:=json.NewEncoder(os.Stdout)
  d:=map[string]int{"apple":5,"lettuce":7}
  enc.Encode(d)
}
複製代碼

Time

Go爲時間和持續時間提供了額外支持。這裏是一些例子。

package main
import "fmt"
import "time"
func main(){
  p:=fmt.Pritnln
  //首先獲取當前時間
  now:=time.Now()
  p(now)
  //你能夠建立一個時間結構體,提供年月日等。
  //時間常常與地區,例如時區關聯
  then:=time.Date(2009,11,17,20,34,58,651387237,time.UTC)
  p(then)
  //能夠按照須要抽取時間變量中的不一樣部分
  p(then.Year())
  p(then.Month())
  p(then.Day())
  p(then.Hour())
  p(then.Minute())
  p(then.Second())
  p(then.Nanosecond())
  p(then.Location())
  p(then.Weekday())
  p(then.Before(now))
  p(then.After(now))
  p(then.Equal(now))
  //sub方法返回兩個時間中的時間長度
  diff:=now.Sub(then)
  p(diff)
  //咱們能夠計算不一樣單位的持續時間長度
  p(diff.Hours())
  p(diff.Minutes())
  p(diff.Seconds())
  p(diff.Nanoseconds())
  //可使用Add來向前推動一個給定的時間
  //或者使用減號來倒退
  p(then.Add(diff))
  p(then.Add(-diff))
}
複製代碼

Epoch

程序中的一個常見需求是獲取秒、毫秒或微秒,自Unix時代,這裏是Go的作法。

package main
import "fmt"
import "time"
func main(){
  now:=time.Now()
  secs:=now.Unix()
  nanos:=now.UnixNano()
  fmt.Println(now)
  millis:=nanos/1000000
  fmt.Println(secs)
  fmt.Println(millis)
  fmt.Println(nanos)
  fmt.Println(time.Unix(secs,0))
  fmt.Println(time.Unix(0,nanos))
}
複製代碼

Time Formatting / Parsing

Go支持時間格式化和解析,根據基於模式的佈局。

go 必須使用2006-01-02 15:04:05來進行格式化,這是go誕生的時間

package main
import "fmt"
import "time"
func main(){
  p:=fmt.Println
  //這裏是一個根據RFC3339基本的格式化時間的例子,使用響應的佈局常量
  t:=time.Now()
  p(t.Format(time.RFC3339))
  //時間解析使用格式化相同的佈局值
  t1,e:=time.Parse(
    time.RFC3339,
    "2012-11-01T22:08:41+00:00"
  )
  p(t1)
  //格式化和解析使用基於示例的佈局
  //一般你使用常量在進行佈局。但你也能夠提供自定義的格式。
  //但你必須使用Mon Jan 2 15:04:05 MST 2006來做爲示例
  p(t.Format("3:04PM"))
  p(t.Format("Mon Jan _2 15:04:05 2006"))
  p(t.Format("2006-01-02T15:04:05.999999-07:00"))
  form:="3 04 PM"
  t2,e:=time.Parse(form,"8 41 PM")
  p(t2)
  //純數字展現你可使用標準的字符串格式化及響應部分的時間值
  fmt.Printf("%d-%02d-%02dT%02d:%02d:%02d-00:00\n",
    t.Year(),t.Month(),t.Day(),
    t.Hour(),t.Minute(),t.Second()
  )
  //解析返回一個錯誤來講明是什麼問題
  ansic:="Mon Jan _2 15:04:05 2006"
  _,e=time.Parse(ansic,"8:41PM")
  p(e)
}
複製代碼

僞隨機數

Go的math/rand包提供產生僞隨機數。

package main
import "time"
import "fmt"
import "math/rand"
func main(){
  //例如,rand.Intn產生一個隨機的整數n,0<=n<100
  fmt.Print(rand.Intn(100),",")
  fmt.Print(rand.Intn(100))
  fmt.Println()
  //rand.Float64返回一個隨機的浮點數,0.0<=f<1.0
  fmt.Println(rand.Float64())
  //這個能夠用於產生其餘區間的浮點數,如5.0<=f<10.0
  fmt.Print((rand.Float64()*5)+5,",")
  fmt.Print((rand.Float64()*5)+5)
  fmt.Println()
  //默認的數字產生器是deterministic,故它每次善生的數字序列是固定的
  //爲了產生不一樣的序列,賦予它一個變化的種子
  //注意,隨即數字用來加密並不安全,用crypto/rand來作
  s1:=rand.NewSource(time.Now().UnixNano())
  r1:=rand.New(s1)
  //調用產生的rand.Rand
  fmt.Print(r1.Intn(100),",")
  fmt.Print(r1.Intn(100))
  fmt.Println()
  //若是你給Source設置了相同的數字種子,他將產生相同的隨即數字序列。
  s2:=rand.NewSrouce(42)
  r2:=rand.New(s2)
  fmt.Print(r2.Intn(100),",")
  fmt.Print(r2.Intn(100))
  fmt.Println()
  s3:=rand.NewSource(42)
  r3:=rand.New(s3)
  fmt.Print(r3.Intn(100),",")
  fmt.Print(r3.Intn(100))
}

複製代碼

Number Parsing

從字符串中解析數字是一個常見的任務,這裏是Go的作法。

package main
//內置的strconv包提供了數字解析
import "strconv"
import "fmt"
func main(){
  //64表明要解析的浮點數精度
  f,_:=strconv.ParseFloat("1.234",64)
  fmt.Println(f)
  //0表明根據字符串推斷基數,64要求結果要適應64位
  i,_:=strconv.ParseInt("123",0,64)
  fmt.Println(i)
  //ParseInt能夠識別十六進制
  d,_:=strconv,Parseint("0x1c8",0,64)
  fmt.Println(d)
  u,_:=strconv.ParseUint("789",0,64)
  fmt.Println(u)
  //atoi是十進制整數解析的簡便函數
  k,_:=strconv.Atoi("135")
  fmt.Println(k)
  //不合法的輸入將致使解析函數返回一個錯誤
  _,e:=strconv.Atoi("wat")
  fmt.Println(e)
}

複製代碼

URL Parsing

package main
import "fmt"
import "net"
import "net/url"
func main(){
  //咱們解析這個示例URL,包含一個協議,受權信息,地址,端口,路徑,查詢參數以及查詢拆分
  s:="postgres://user:pass@host.com:5432/path?k=v#f"
  //解析這個URL而且保證沒有錯誤
  u,err:=url.Parse(s)
  if err!=nil{
    panic(err)
  }
  //能夠直接訪問協議
  fmt.Println(u.Scheme)
  //User包含全部受權信息,調用Username和Password能夠獲得單獨的值
  fmt.Println(u.User)
  fmt.Println(u.User.Username())
  p,_:=u.User.Password()
  fmt.Println(p)
  //Host包含地址和端口。使用SplitHostPort來抽取他們
  fmt.Println(u.Host)
  host,port,_:=net.SplitHostPort(u.Host)
  fmt.Println(host)
  fmt.Println(port)
  fmt.Println(u.Path)
  fmt.Println(u.Fragment)
  //爲了以k=v的格式獲得查詢參數,使用RawQuery
  //你也能夠將查詢參數解析到一個map中
  //解析的查詢參數是從字符串到字符串的片斷,故索引0能夠只獲得第一個值
  fmt.Println(u.RawQuery)
  m,_:=url.ParseQuery(u.RawQuery)
  fmt.Println(m)
  fmt.Println(m["k"][0])
}

複製代碼

SHA1 Hashes

SHA1哈希常常用於計算二進制或者文本塊的短標識。例如,git版本控制系統使用SHA1來標示文本和目錄。這裏是Go如何計算SHA1哈希值。

package main
//Go實現了多種哈希函數,在crypto包下
import "crypto/sha1"
import "fmt"
func main(){
  s:="sha1 this string"
  //產生一個哈希的模式是sha1.New(),sha1.Write(bytes)而後sha1.Sum([]bytes{})
  h:=sha1.New()
  h.Write([]byte(s))
  //這裏獲得最終的哈希結果字節片斷值。參數用於向已存在的字節片斷追加,一般不須要
  bs:=h.Sum(nil)
  //SHA1值常常用於打印成十六進制,如git提交時。使用%x格式參數來轉換爲十六進制
  fmt.Println(s)
  fmt.Printf("%x\n",bs)
}

複製代碼

Base64 Encoding

package main
//這裏爲引入的包起了名稱,來節省下面代碼的空間
import b64 "encoding/base64"
import "fmt"
func main(){
  data:="abc123!?$*&()'-=@~"
  sEnc:=b64.StdEncoding.EncodeToString([]byte(data))
  fmt.Println(sEnc)
  sDec,_:=b64.StdEncoding.DecodeString(sEnc)
  fmt.Println(string(sDec))
  fmt.Println()
  uEnc:=b64.URLEncoding.EncodeToString([]byte(data))
  fmt.Println(uEnc)
  uDec,_:=b64.URLEncoding.DecodeString(uEnc)
  fmt.Println(string(uDec))
}

複製代碼

Reading Files

讀取和寫入文件是許多Go程序的基本任務需求。首先咱們來看讀取文件。

package main
import(
  "bufio"
  "fmt"
  "io"
  "io/ioutil"
  "os"
)
//讀取文件須要檢查大多數調用是否錯誤,這個helper能夠流水化錯誤檢查
func check(e error){
  if e!=nil{
    panic(e)
  }
}
func main(){
  //最基本的一個文件讀取任務是將全部內容放入內存中
  dat,err:=ioutil.ReadFile("/tmp/dat")
  check(err)
  fmt.Print(string(dat))
  //若是你想對文件的那部分如何進行讀取有更多的控制
  //首先你須要打開它
  f,err:=os.Open("/tmp/dat")
  check(err)
  //從文件的開頭讀取一些字節。容許到5,同時也是實際讀取了的字節。
  b1:=make([]byte,5)
  n1,err:=f.Read(b1)
  check(err)
  fmt.Printf("%d bytes: %s\n",n1,string(b1))
  //你也能夠找到一個已知的位置並從那裏開始讀取
  o2,err:=f.Seek(6,0)
  check(err)
  b2:=make([]byte,2)
  n2,err:=f.Read(b2)
  check(err)
  fmt.Printf("%d bytes @ %d: %s\n",n2,o2,string(b2))
  //io包提供了一些函數,對於文件讀取可能頗有幫助
  //例如上面的繼續讀取的例子,可使用ReadAtLeast更穩定的實現
  o3,err:=f.Seek(6,0)
  check(err)
  b3:=make([]byte,2)
  n3,err:=io.ReadAtLeast(f,b3,2)
  check(err)
  fmt.Printf("%d bytes @ %d: %s\n",n3,o3,string(b3))
  //沒有內置的退回,可是Seek(0,0)完成了這個事情
  _,err=f.Seek(0,0)
  check(err)
  //bufio包實現了一個帶緩衝區的讀取,它對於一些小的讀取以及因爲它所提供的額外方法會頗有幫助
  r4:=bufio.NewReader(f)
  b4,err:=r4.Peek(5)
  check(err)
  fmt.Printf("5 bytes: %s\n",string(b4))
  //在完成時關閉文件(一般會在打開時經過defer計劃執行)
  f.Close()
}

複製代碼

Writing Files

寫文件與讀取的模式相似。

package main
import(
  "bufio"
  "fmt"
  "io/ioutil"
  "os"
)
func check(e error){
  if e!=nil{
    panic(e)
  }
}
func main(){
  //這裏將一個字符串(或者僅僅是字節)寫入到一個文件。
  d1:=[]byte("hello\ngo\n")
  err:=ioutil.WriteFile("/tmp/dat1",d1,0644)
  check(err)
  //打開一個文件以供寫入
  f,err:=os.Create("/tmp/dat2")
  check(err)
  //這是一個慣用法,在打開時馬上經過defer關閉
  defer f.Close()
  d2:=[]byte{115,111,109,101,10}
  n2,err:=f.Write(d2)
  check(err)
  fmt.Printf("wrote %d bytes\n",n2)
  n3,err:=f.WriteString("writes\n")
  fmt.Printf("wrote %d bytes\n",n3)
  f.Sync()
  w:=bufio.NewWriter(f)
  n4,err:=w.WriteString("buffered\n")
  fmt.Printf("wrote %d bytes\n",n4)
  w.Flush()
}

複製代碼

Line Filters

一個行過濾器常常見於讀取標準輸入流的輸入,處理而後輸出到標準輸出的程序中。grep和sed是常見的行過濾器。

//下面這個行過濾器示例將全部輸入的文字轉換爲大寫的版本
package main
import (
  "bufio"
  "fmt"
  "os"
  "strings"
)
func main(){
  //使用一個帶緩衝的scanner能夠方便的使用Scan方法來直接讀取一行
  //每次調用該方法可讓scanner讀取下一行
  scanner:=bufio.NewScanner(os.Stdin)
  //Text方法返回當前的token,如今是輸入的下一行
  for scanner.Scan(){
    ucl:=strings.ToUpper(scanner.Text())
    //輸出大寫的行
    fmt.Println(ucl)
  }
  //檢查scanner的錯誤,文件結束符不會被看成是一個錯誤
  if err:=scanner.Err();err!=nil{
    fmt.Fprintln(os.Stderr,"error:",err)
    os.Exit(1)
  }
}

複製代碼

可使用以下命令來試驗這個行過濾器:

$ echo 'hello' > /tmp/lines
$ echo 'filter' >> /tmp/lines
$ cat /tmp/lines | go run line-filters.go

複製代碼

Command-Line Arguments

package main
import "os"
import "fmt"
func main(){
  //os.Args提供原始命令行參數訪問功能
  //切片的第一個值是程序的路徑
  argsWithProg:=os.Args
  //os.Args[1:]保存程序的全部參數
  argsWithoutProg:=os.Args[1:]
  //你能夠經過天然索引獲取到每一個單獨的參數
  arg:=os.Args[3]
  fmt.Println(argsWithProg)
  fmt.Println(argsWithoutProg)
  fmt.Println(arg)
}

複製代碼

本例應當先go build,而後再運行並指定參數

Command-Line Flags

命令行標誌是一個指定特殊選項的經常使用方法。例如,在wc -l的-l就是一個命令行標誌。

package main
//flag包支持基本的命令行標誌解析
import "flag"
import "fmt"
func main(){
  //基本的標誌聲明僅支持字符串、整數和布爾值選項
  //這裏聲明瞭一個默認值爲foo的字符串標誌word,並帶有一個簡短的描述。flag.String返回一個字符串指針。
  wordPtr:=flag.String("word","foo","a string")
  //相似聲明一個整數和布爾值標誌。
  numbPtr:=flag.Int("numb",42,"an int")
  boolPtr:=flag.Bool("fork",false,"a bool")
  //可使用程序中已有的參數聲明一個標誌,聲明時須要指定該參數的指針
  var svar string
  flag.StringVar(&svar,"svar","bar","a string var")
  //全部標誌聲明完成後,調用flag.Parse()來執行命令行解析
  flag.Parse()
  //經過對指針解引用來獲取選項的實際值
  fmt.Println("word:",*wordPtr)
  fmt.Println("numb:",*numbPtr)
  fmt.Println("fork:",*boolPtr)
  fmt.Println("svar:",svar)
  fmt.Println("tail:",flag.Args())
}

複製代碼

測試用例:

$ go build command-line-flags.go
# 省略的標誌將自動設定爲默認值
$ ./command-line-flags -word=opt
# 位置參數能夠出如今任何標誌後面
$ ./command-line-flags -word=opt a1 a2 a3
# flag包須要的全部標誌出如今位置參數以前,不然標誌將會被解析爲位置參數
$ ./command-line-flags -word=opt a1 a2 a3 -numb=7
# 使用-h或者--help標誌來獲得自動生成的命令行幫助文本
$ ./command-line-flags -h
# 若是提供了一個沒有用flag包指定的標誌,將會獲得錯誤信息和幫助文檔
$ ./command-line-flags -wat

複製代碼

環境變量

package main
import "os"
import "strings"
import "fmt"
func main(){
  //使用os.Setenv來設置一個鍵值對
  //使用os.Getenv來獲取一個環境變量,若是不存在,返回空字符串
  os.Setenv("FOO","1")
  fmt.Println("FOO:",os.Getenv("FOO"))
  fmt.Println("BAR:",os.Getenv("BAR"))
  fmt.Println()
  //使用os.Environ來列出全部環境變量鍵值對
  for _,e:=range os.Environ(){
    pair:=strings.Split(e,"=")
    fmt.Prinln(pair[0])
  }
}

複製代碼

Spawning Processes

package main
import "fmt"
import "io/iouitl"
import "os/exec"
func main(){
  //exec.Command函數幫助咱們建立一個表示這個外部進程的對象
  dateCmd:=exec.Command("date")
  //Output等待命令運行完成,並收集命令的輸出
  dateOut,err:=dateCmd.Output()
  if err!=nil{
    panic(err)
  }
  fmt.Println("> date")
  fmt.Println(string(dateOut))
  grepCmd:=exec.Command("grep","hello")
  //獲取輸入輸出管道
  grepIn,_:=grepCmd.StdinPipe()
  grepOut,_:=grepCmd.StdoutPipe()
  //運行進程,寫入輸入信息,讀取輸出結果,等待程序運行結束
  grepCmd.Start()
  grepIn.Write([]byte("hello grep\ngoodbye grep"))
  grepIn.Close()
  grepByte,_:=ioutil.ReadAll(grepOut)
  grepCmd.Wait()
  fmt.Println("> grep hello")
  fmt.Println(string(grepBytes))
  //經過bash命令的-c選項來執行一個字符串包含的完整命令
  lsCmd:=exec.Command("bash","-c","ls -a -l -h")
  lsOut,err:=lsCmd.Output()
  if err!=nil{
    panic(err)
  }
  fmt.Println("> ls -a -l -h")
  fmt.Pritnln(string(lsOut))
}

複製代碼

Execing Processes

package main
import "syscall"
import "os"
import "os/exec"
func main(){
  //經過LookPath獲得須要執行的可執行文件的絕對路徑
  binary,lookErr:=exec.LookPath("ls")
  if lookErr!=nil{
    panic(lookErr)
  }
  //Exec須要的參數是切片形式的,第一個參數爲執行程序名
  args:=[]string{"ls","-a","-l","-h"}
  env:=os.Environ()
  execErr:=syscall.Exec(binary,args,env)
  if execErr!=nil{
    panic(execErr)
  }
}

複製代碼

Signals

package main
import "fmt"
import "os"
import "os/signal"
import "syscall"
func main(){
  //Go經過向一個通道發送os.Signal值來進行信號通知
  sigs:=make(chan os.Signal,1)
  //同時建立一個用於在程序能夠結束時進行通知的通道
  done:=make(chan bool,1)
  //註冊給定通道用於接收特定信號
  signal.Notify(sigs,syscall.SIGINT,syscall.SIGTERM)
  //Go協程執行一個阻塞的信號接收操做,當它獲得一個值時,打印並通知程序能夠退出
  go func(){
    sig:=<-sigs
    fmt.Println()
    fmt.Println(sig)
    done<-true
  }()
  fmt.Println("awaiting signal")
  <-done
  fmt.Println("exiting")
}

複製代碼

運行,使用ctrl-c發送信號

Exit

package main
import "fmt"
import "os"
func main(){
  //當使用os.Exit時,defer將不會執行
  defer fmt.Println("!")
  //退出,而且狀態爲3
  os.Exit(3)
}

複製代碼
相關文章
相關標籤/搜索