這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Introduce
這是來自於go by example的例子,花了幾天的時間寫完了這些例子,感覺對我的協助很大,對於初學者來說,我的建議還是先找本go的書從頭到尾看一下,然後再來看這些例子,每個例子都手敲一遍,對你的協助還是很大的。在敲這些例子的過程中,有一些疑問,也有一些知識的擴充,因此總結了本文。
time和channel
golang的time package帶有定時器的功能,而定時器和channel完美融合,建立一個定時器會返回一個channel,在定時器到期之前讀這個channel是阻塞的,直到定時時間到達這個channel就會變成可讀的。
func main() { //建立了一個定時器,2秒後會發送事件到timer1.C channel timer1 := time.NewTimer(time.Second * 2) //等待定時器到期 data := <-timer1.C //接收到的資料 2016-07-16 15:24:19.337701998 +0800 CST fmt.Println("Timer 1 expired") fmt.Println("Timer 1 expired",data) timer2 := time.NewTimer(time.Second) go func() { <- timer2.C fmt.Println("Timer 2 expired") }() //關閉定時器 stop2 := timer2.Stop() if stop2 { fmt.Println("Timer 2 stopped") }}
time package除了具有定時器的功能外,還有一個Ticker,Ticker同樣也是和channel完美融合的一個功能,建立一個Ticker會返回channel
通過range這個channel。來表示每次interval的到來。再結合go的協程就很容易實現一個定時任務的功能。
func main() { //建立了定時器,每time.Millisecond * 500就產生事件,發送到chnanel ticker := time.NewTicker(time.Millisecond * 500) go func() { for t := range ticker.C { fmt.Println("Tick at",t) } }() //睡上一段事件,然後關閉 time.Sleep(time.Millisecond * 1600) ticker.Stop() fmt.Println("Ticker stopped")}
goroutines和work pool
goroutines結合channel很容易就可以實現一個work pool,開上N個goroutines,然後這N個goroutines共同去讀channel,讀到channel
就去執行相應的工作,然後結果通過另外一個channel傳出來即可,模型很簡單。用go實現起來還是很容易的。
func worker(id int,jobs <-chan int,result chan<- int) { for j := range jobs { fmt.Println("worker",id,"processing job",j) time.Sleep(time.Second) result <- j * 2 }}func main() { jobs := make(chan int,100) results := make(chan int,100) //啟動三個worker for w := 1; w <= 3; w++ { go worker(w,jobs,results) } //迴圈9次,發送任務 for j := 1; j <= 9;j++ { jobs <- j } close(jobs) //迴圈得到結果 for a := 1; a <= 9; a++ { <-results }}
rate limiting與channel
限速這是一個用於控制資源使用率和保證服務品質的一種機制,golang通過goroutines,channel還有tickers優雅的支援了這個機制。比如處理web請求的限速,每接收一個請求就先讀取一個tick,這個tick每隔固定時間才可讀,這樣就可以實現限速的功能。
func main() { requests := make(chan int,5) //發送五條訊息 for i := 1; i <= 5;i++ { requests <- i } close(requests) //建立了定時器,然後遍曆channel,列印資訊 limiter := time.Tick(time.Millisecond * 200) for req := range requests { <-limiter //每隔time.Millisecond * 200,起到了限速的作用 fmt.Println("requests",req,time.Now()) }}
但是上面的限速存在一個問題,就是並發數只有1,如果可以在擁有固定的並發數的情況下限速呢?,這就需要藉助channel的buffer功能了。上面的time.Tick返回的channel是沒有buffer的,所以一次只能處理一個請求,如果這個channel是有buffer的,比如這個buffer的大小是N那麼可以同時並發接收N個請求,想處理第N+1個請求就需要等待固定時間才可以。
func main() { //建立了另外一個time.Time類似的channel burstyLimiter := make(chan time.Time,3) //發送三個 for i := 0; i < 3; i++ { burstyLimiter <- time.Now() } go func() { for t := range time.Tick(time.Millisecond * 200) { burstyLimiter <- t //每200 * time.Millisecond 就發送一個事件到burstyLimiter } }() burstyRequests := make(chan int,5) for i := 1; i <= 5; i++ { burstyRequests <- i //發送五個資料 } close(burstyRequests) for req := range burstyRequests { //現在開始限速讀取 <-burstyLimiter//在讀前三個的時候是不會阻塞的,直到讀取第四個的 時候才開始通過Limiter限速 fmt.Println("request",req,time.Now()) }}
自訂sort和Interface
golang的sort package內建排序的功能,但是如果要對使用者自己定義的資料結構進行排序這就不好半了,在C++中要求使用者對關係運算子重載即可在golang中則需要和interface完美融合,只要使用者實現Len,Less,Swap三個介面即可,就是這麼簡單。
package mainimport "fmt"import "sort"//string slice的別名,給這個別名struct 添加方法type ByLength []stringfunc (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])}//sort介面需要實現 Swap Less和len即可func main() { fruits := []string{"peach","banana","kiwi"} sort.Sort(ByLength(fruits)) fmt.Println(fruits)}
signal和channel
golang再一次將unix上的signals和channel結合了起來,unix上通過給訊號註冊處理函數來完成訊號處理,在golang中,通過把訊號和channel關聯起來,當有訊號到來channel就可讀了。返回的結果就是signal的號碼。
import "fmt"import "os"import "os/signal"import "syscall"func main() { //os.Signal類型的chn sigs := make(chan os.Signal,1) done := make(chan bool,1) //通過Norify來註冊訊號, signal.Notify(sigs,syscall.SIGINT,syscall.SIGTERM) //將SIGINT和SIGTERM和sigs channel結合起來 //協成來收集訊號,然後發送done chan來表示完成 go func() { sig := <-sigs fmt.Println() fmt.Println(sig) done <- true }() fmt.Println("awaiting signal") <-done fmt.Println("exiting")}