Go語言併發的設計模式和應用場景

生成器

  在Python中咱們能夠使用yield關鍵字來讓一個函數成爲生成器,在Go中咱們能夠使用信道來製造生成器(一種lazy load相似的東西)。html

  固然咱們的信道並非簡單的作阻塞主線的功能來使用的哦。數據庫

  下面是一個製做自增整數生成器的例子,直到主線向信道索要數據,咱們才添加數據到信道:併發

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) } 

多路複合

上面的例子都使用一個信道做爲返回值,能夠把信道的數據合併到一個信道的。 不過這樣的話,咱們須要按順序輸出咱們的返回值(先進先出)。ui

以下,咱們假設要計算很複雜的一個運算 100-x , 分爲三路計算, 最後統一在一個信道中取出結果:spa

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,用於監測各個信道的數據流動。code

以下的程序是select的一個使用例子,咱們監視三個信道的數據流出並收集數據到一個信道中。htm

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之內的素數(你須要先知道什麼是篩法)

程序有詳細的註釋,再也不說明了。

/*  * 利用信道菊花鏈篩法求某一個整數範圍的素數  * 篩法求素數的基本思想是:把從1開始的、某一範圍內的正整數從小到大順序排列,  * 1不是素數,首先把它篩掉。剩下的數中選擇最小的數是素數,而後去掉它的倍數。  * 依次類推,直到篩子爲空時結束  */ package main import "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 main import ( "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的應用場景例子。

相關文章
相關標籤/搜索