• go语言】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
    ", <-c)
        }
        fmt.Println("You're boring; I'm leaving.")
    }

    接下来,我会base于上面的这个例子,来介绍各种patterns。

    • 生成器(Generator)

    由于go中的channel也是一种变量,所以我们可以通过返回channel的方式来传递结果

    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
    ", <-c)
        }
        fmt.Println("You're boring; I'm leaving.")
    }

    通过这个例子,我们可以很容易想到其他运用返回结果channel的例子,这样做不仅使得程序更加的清晰,而且更加有利于的非阻塞过程的组织,因为我们可以在任何必要的时候通过结果channel读取结果。如此一来,我们可以将boring作为一种服务,就像下面的例子:

    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读出的结果数据将是随机的。下面这张图很形象地的展现了多路复合模式的过程。

    • 选择(Select)

    Go中的select其实和Unix/Linux下的多路复用的select在思想上有异曲同工之妙,我们可以通过Select来做很多很美妙的事情。首先,我们来改写fanin方法,把它改写为使用select的版本:

    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并发开销,大大增大并发吞吐量。

    • 超时(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就可以很方便得实现超时处理的功能。

    • 退出

    那么我们怎么来控制一个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会显得十分方便。

  • 相关阅读:
    Leo程序员羊皮卷文摘(更新ing)
    ubuntu下的yuv播放器
    浏览器之一
    海量数据处理常用思路和方法(zh)
    我本将心向明月,奈何明月照沟渠
    转载光纤通信之父
    重装系统或是更换电脑之后,Foxmail的恢复
    关于录制Linux视频
    Linux之路(原发表于07年,现在搬到博客)
    Gentoo安装 miniCD+stage3
  • 原文地址:https://www.cnblogs.com/tangchuanyang/p/7464621.html
Copyright © 2020-2023  润新知