【go語言】Goroutines 併發模式(二)

前言

Goroutines 併發模式(一)中,咱們簡單地經過boring函數的例子來粗略地闡述了經過channels來和goroutines交流的方法。在本篇中,我將從pattern的方向出發,經過對boring函數的例子進行各類改寫,來說解幾種常見了goroutines的併發模式。併發


併發模式

讓咱們先來回顧一下boring函數的例子。函數

func boring(msg string, c chan string) {
   for i := 0; ; i++ {
        c <- fmt.Sprintf("%s %d", msg, i)
        time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
	}
}
           
func main() {	
	c := make(chan string)
	go boring("boring!", c)
 	for i := 0; i < 5; i++ {
    	fmt.Printf("You say: %q\n", <-c)
    }
    fmt.Println("You're boring; I'm leaving.")
}

接下來,我會base於上面的這個例子,來介紹各類patterns。性能

  • 生成器(Generator)

因爲go中的channel也是一種變量,因此咱們能夠經過返回channel的方式來傳遞結果ui

func boring(msg string) <-chan string { 
    c := make(chan string)
    go func() { 
        for i := 0; ; i++ {
            c <- fmt.Sprintf("%s %d", msg, i)
            time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
        }
    }()
    return c 
}
func main(){
	c := boring("boring!") 
    for i := 0; i < 5; i++ {
        fmt.Printf("You say: %q\n", <-c)
    }
    fmt.Println("You're boring; I'm leaving.")
}

經過這個例子,咱們能夠很容易想到其餘運用返回結果channel的例子,這樣作不只使得程序更加的清晰,並且更加有利於的非阻塞過程的組織,由於咱們能夠在任何須要的時候經過結果channel讀取結果。如此一來,咱們能夠將boring做爲一種服務,就像下面的例子:spa

func main() {
    joe := boring("Joe")
    ann := boring("Ann")
    for i := 0; i < 5; i++ {
        fmt.Println(<-joe)
        fmt.Println(<-ann)
    }
    fmt.Println("You're both boring; I'm leaving.")
}
  • 多路複合(Multiplexing)

func fanIn(input1, input2 <-chan string) <-chan string {
    c := make(chan string)
    go func() { for { c <- <-input1 } }()
    go func() { for { c <- <-input2 } }()
    return c
}
func main() {
    c := fanIn(boring("Joe"), boring("Ann"))
    for i := 0; i < 10; i++ {
        fmt.Println(<-c)
    }
    fmt.Println("You're both boring; I'm leaving.")
}

咱們經過fanIn函數將兩個boring函數返回的結果channel給複合到了一個channel中,這樣咱們能夠看到在main函數中經過複合後的channel讀出的結果數據將是隨機的。下面這張圖很形象地的展示了多路複合模式的過程。.net


  • 選擇(Select)

Go中的select其實和Unix/Linux下的多路複用的select在思想上有殊途同歸之妙,咱們能夠經過Select來作不少很美妙的事情。首先,咱們來改寫fanin方法,把它改寫爲使用select的版本:code

func fanIn(input1, input2 <-chan string) <-chan string {
    c := make(chan string)
    go func() {
        for {
            select {
            case s := <-input1:  c <- s
            case s := <-input2:  c <- s
            }
        }
    }()
    return c
}

這裏的select將同時監聽多個channel,只要有其中一個channel能夠讀取數據,那麼select就將解除阻塞狀態,運行相應case下的代碼。若是您寫過一些高性能的併發程序,那麼您必定早就發現select真乃神器,select不只能夠簡化代碼清晰邏輯,並且能夠減小IO併發開銷,大大增大併發吞吐量。blog

  • 超時(Timeout)

在goroutines中,有時候可能會由於等待某個channel而長期阻塞某個goroutine,因此咱們須要爲之增長超時的功能。下面例子將使用select實現超時功能。遊戲

func main() {
    c := boring("Joe")
    for {
        select {
        case s := <-c:
            fmt.Println(s)
        case <-time.After(1 * time.Second):
            fmt.Println("You're too slow.")
            return
        }
    }
}

這裏的time是go提供的一個庫,After方法將返回一個在相應時間以後能夠讀取的channel,這樣咱們使用select就能夠很方便得實現超時處理的功能。ip

  • 退出

那麼咱們怎麼來控制一個goroutine,使它能夠結束本身的使命正常結束呢?其實很簡單,一樣咱們使用select來實現這個功能。

func boring(msg string, quit chan bool) <-chan string { 
    c := make(chan string)
    go func() { 
        for i := 0; ; i++ {
        	select {
        	case c <- fmt.Sprintf("%s: %d", msg, i):
        		time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)	
        	case <-quit:
        		return
        	}
        }
    }()
    return c
}
func main(){
	quit := make(chan bool)
    c := boring("Joe", quit)
    for i := rand.Intn(10); i >= 0; i-- { fmt.Println(<-c) }
    quit <- true
}

經過在boring的循環中增長一個select,在main中咱們即可以經過向quit 寫入數據的方式來控制boring的退出。換句話來說,其實就是作到了不一樣goroutines間的一個交流罷了。

  • 菊花鏈(Daisy-chain)

要說清楚什麼是菊花鏈,讓咱們先看一幅圖

咱們看圖說話,圖中的gopher是一個一個channel,這些channel從頭至尾連了起來。但咱們把一個數據放到channel的頭部的時候,經過傳遞,咱們即可以從channel的尾部讀出數據。是否是以爲這很像你們小時候玩的傳悄悄話的遊戲??具體實例以下:

func f(left, right chan int) {
    left <- 1 + <-right
}

func main() {
    const n = 100000
    leftmost := make(chan int)
    right := leftmost
    left := leftmost
    for i := 0; i < n; i++ {
        right = make(chan int)
        go f(left, right)
        left = right
    }
    go func(c chan int) { c <- 1 }(right)
    fmt.Println(<-leftmost)
}

上面代碼初始化了100000個channel,並把他們按照順序鏈接起來。最後向最右邊的channel寫入一個數據,從最左邊的channel讀出來。這種菊花鏈的模型很是適合做爲過濾器filter來使用,經過channel來鏈接filter會顯得十分方便。

相關文章
相關標籤/搜索