這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
產生器
在Python中我們可以使用yield關鍵字來讓一個函數成為產生器,在Go中我們可以使用通道來製造產生器(一種lazy load類似的東西)。
當然我們的通道並不是簡單的做阻塞主線的功能來使用的哦。
下面是一個製作自增整數產生器的例子,直到主線向通道索要資料,我們才添加資料到通道:
func xrange() chan int{ // xrange用來產生自增的整數 var ch chan int = make(chan int) go func() { // 開出一個goroutine for i := 0; ; i++ { ch <- i // 直到通道索要資料,才把i添加進通道 } }() return ch}func main() { generator := xrange() for i:=0; i < 1000; i++ { // 我們產生1000個自增的整數! fmt.Println(<-generator) }}
這不禁叫我想起了Python中可愛的xrange, 所以給了產生器這個名字!
服務化
比如我們載入一個網站的時候,例如我們登入新浪微博,我們的訊息資料應該來自一個獨立的服務,這個服務只負責 返回某個使用者的新的訊息提醒。
如下是一個使用樣本:
func get_notification(user string) chan string{ /* * 此處可以查詢資料庫擷取新訊息等等.. */ notifications := make(chan string) go func() { // 懸掛一個通道出去 notifications <- fmt.Sprintf("Hi %s, welcome to weibo.com!", user) }() return notifications}func main() { jack := get_notification("jack") // 擷取jack的訊息 joe := get_notification("joe") // 擷取joe的訊息 // 擷取訊息的返回 fmt.Println(<-jack) fmt.Println(<-joe)}
多路複合
上面的例子都使用一個通道作為傳回值,可以把通道的資料合併到一個通道的。 不過這樣的話,我們需要按順序輸出我們的傳回值(先進先出)。
如下,我們假設要計算很複雜的一個運算 100-x , 分為三路計算, 最後統一在一個通道中取出結果:
func do_stuff(x int) int { // 一個比較耗時的事情,比如計算 time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond) //類比計算 return 100 - x // 假如100-x是一個很費時的計算}func branch(x int) chan int{ // 每個分支開出一個goroutine做計算並把計算結果流入各自通道 ch := make(chan int) go func() { ch <- do_stuff(x) }() return ch}func fanIn(chs... chan int) chan int { ch := make(chan int) for _, c := range chs { // 注意此處明確傳值 go func(c chan int) {ch <- <- c}(c) // 複合 } return ch}func main() { result := fanIn(branch(1), branch(2), branch(3)) for i := 0; i < 3; i++ { fmt.Println(<-result) }}
select監聽通道
Go有一個語句叫做select,用於監測各個通道的資料流動。
如下的程式是select的一個使用例子,我們監視三個通道的資料流出並收集資料到一個通道中。
func foo(i int) chan int { c := make(chan int) go func () { c <- i }() return c}func main() { c1, c2, c3 := foo(1), foo(2), foo(3) c := make(chan int) go func() { // 開一個goroutine監視各個通道資料輸出並收集資料到通道c for { select { // 監視c1, c2, c3的流出,並全部流入通道c case v1 := <- c1: c <- v1 case v2 := <- c2: c <- v2 case v3 := <- c3: c <- v3 } } }() // 阻塞主線,取出通道c的資料 for i := 0; i < 3; i++ { fmt.Println(<-c) // 從列印來看我們的資料輸出並不是嚴格的1,2,3順序 }}
有了select, 我們在 多路複合中的範例程式碼中的函數fanIn還可以這麼來寫(這樣就不用開好幾個goroutine來取資料了):
func fanIn(branches ... chan int) chan int { c := make(chan int) go func() { for i := 0 ; i < len(branches); i++ { //select會嘗試著依次取出各個通道的資料 select { case v1 := <- branches[i]: c <- v1 } } }() return c}
使用select的時候,有時需要逾時處理, 其中的timeout通道相當有趣:
timeout := time.After(1 * time.Second) // timeout 是一個計時通道, 如果達到時間了,就會發一個訊號出來for is_timeout := false; !is_timeout; { select { // 監視通道c1, c2, c3, timeout通道的資料流出 case v1 := <- c1: fmt.Printf("received %d from c1", v1) case v2 := <- c2: fmt.Printf("received %d from c2", v2) case v3 := <- c3: fmt.Printf("received %d from c3", v3) case <- timeout: is_timeout = true // 逾時 }}
結束標誌
在Go並發與並行筆記一我們已經講過通道的一個很重要也很平常的應用,就是使用無緩衝通道來阻塞主線,等待goroutine結束。
這樣我們不必再使用timeout。
那麼對上面的timeout來結束主線的方案作個更新:
func main() { c, quit := make(chan int), make(chan int) go func() { c <- 2 // 添加資料 quit <- 1 // 發送完成訊號 } () for is_quit := false; !is_quit; { select { // 監視通道c的資料流出 case v := <-c: fmt.Printf("received %d from c", v) case <-quit: is_quit = true // quit通道有輸出,關閉for迴圈 } }}
菊花鏈
簡單地來說,資料從一端流入,從另一端流出,看上去好像一個鏈表,不知道為什麼要取這麼個尷尬的名字。。
菊花鏈的英文名字叫做: Daisy-chain, 它的一個應用就是做過濾器,比如我們來篩下100以內的素數(你需要Crowdsourced Security Testing道什麼是篩法)
程式有詳細的注釋,不再說明了。
/* * 利用通道菊花鏈篩法求某一個整數範圍的素數 * 篩法求素數的基本思想是:把從1開始的、某一範圍內的正整數從小到大順序排列, * 1不是素數,首先把它篩掉。剩下的數中選擇最小的數是素數,然後去掉它的倍數。 * 依次類推,直到篩子為空白時結束 */package mainimport "fmt"func xrange() chan int{ // 從2開始自增的整數產生器 var ch chan int = make(chan int) go func() { // 開出一個goroutine for i := 2; ; i++ { ch <- i // 直到通道索要資料,才把i添加進通道 } }() return ch}func filter(in chan int, number int) chan int { // 輸入一個整數隊列,篩出是number倍數的, 不是number的倍數的放入輸出隊列 // in: 輸入隊列 out := make(chan int) go func() { for { i := <- in // 從輸入中取一個 if i % number != 0 { out <- i // 放入輸出通道 } } }() return out}func main() { const max = 100 // 找出100以內的所有素數 nums := xrange() // 初始化一個整數產生器 number := <-nums // 從產生器中抓一個整數(2), 作為初始化整數 for number <= max { // number作為篩子,當篩子超過max的時候結束篩選 fmt.Println(number) // 列印素數, 篩子即一個素數 nums = filter(nums, number) //篩掉number的倍數 number = <- nums // 更新篩子 }}
隨機數產生器
通道可以做產生器使用,作為一個特殊的例子,它還可以用作隨機數產生器。如下是一個隨機01產生器:
func rand01() chan int { ch := make(chan int) go func () { for { select { //select會嘗試執行各個case, 如果都可以執行,那麼隨機選一個執行 case ch <- 0: case ch <- 1: } } }() return ch}func main() { generator := rand01() //初始化一個01隨機產生器 //測試,列印10個隨機01 for i := 0; i < 10; i++ { fmt.Println(<-generator) }}
定時器
我們剛才其實已經接觸了通道作為定時器, time包裡的After會製作一個定時器。
看看我們的定時器吧!
/* * 利用通道做定時器 */package mainimport ( "fmt" "time")func timer(duration time.Duration) chan bool { ch := make(chan bool) go func() { time.Sleep(duration) ch <- true // 到時間啦! }() return ch}func main() { timeout := timer(time.Second) // 定時1s for { select { case <- timeout: fmt.Println("already 1s!") // 到時間 return //結束程式 } }}
TODO
Google的應用情境例子。