【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会显得十分方便。

相关文章
相关标签/搜索