并发模式

让我们先来回顾一下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的方式来传递结果

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作为一种服务,就像下面的例子:

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

go语言】Goroutines 并发模式的更多相关文章

  1. Go语言-并发模式-资源池实例(pool)

    Go语言并发模式 利用goroutine和channel进行go的并发模式,实现一个资源池实例(<Go语言实战>书中实例稍作修改) 资源池可以存储一定数量的资源,用户程序从资源池获取资源进 ...

  2. 《Go语言实战》摘录:7.2 并发模式 - pool

    7.2 并发模式 - pool

  3. 《Go语言实战》摘录:7.3 并发模式 - work

    7.3 并发模式 - work

  4. 《Go语言实战》摘录:7.1 并发模式 - runner

    7.1 并发模式 - runner

  5. go语言之并发

    简介           多核处理器越来越普及,那有没有一种简单的办法,能够让我们写的软件释放多核的威力?答案是:Yes.随着Golang, Erlang, Scale等为并发设计的程序语言的兴起,新 ...

  6. 探索 Java 同步机制[Monitor Object 并发模式在 Java 同步机制中的实现]

    探索 Java 同步机制[Monitor Object 并发模式在 Java 同步机制中的实现] https://www.ibm.com/developerworks/cn/java/j-lo-syn ...

  7. Go并发模式:管道与取消

    关键字:Go语言,管道,取消机制,并发,sync.WaitGroup,包引用,通道,defer,select GO并发模式:管道与取消 简介 Go的并发能力可以使构建一个流数据管道变得非常容易,并且可 ...

  8. 16 Go Concurrency Patterns: Timing out, moving on GO并发模式: 超时, 继续前进

    Go Concurrency Patterns: Timing out, moving on  GO并发模式: 超时, 继续前进 23 September 2010 Concurrent progra ...

  9. [转] Go 的并发模式:Context

    [转] Go 的并发模式:Context tips:昨天看了飞雪无情的关于 Context 的文章,对 go 中 Context 有了一个初步的认识.今天看到一个 go 官方博客的关于 Context ...

随机推荐

  1. 混合开发的大趋势之一React Native之页面跳转

    转载请注明出处:王亟亟的大牛之路 最近事情有点多,没有长时间地连贯学习,文章也停了一个多礼拜,愧疚,有时间还是继续学习,继续写! 还是先安利:https://github.com/ddwhan0123 ...

  2. windows环境下mysql的解压安装以及备份和还原

    系统环境为server2012 1.下载mysql解压版,解压安装包到指定目录 2.在以上目录中,复制一份my-default.ini文件,重命名为my.ini,进行如下修改(按照需要): [mysq ...

  3. 解题报告: hdu 3949 - 线性基

    #include <iostream> #include <cstdio> #define LL long long using namespace std; const in ...

  4. ACM输入函数测试 - scanf cin 优化的输入

    2017-08-27 10:26:19 writer:pprp 进行测试如下四种输入方式: 1.scanf 2.cin 3.用了ios::sync_with_stdio(false);的cin 4.自 ...

  5. Python 基础教程

    Python 基础教程 Python是一种解释型.面向对象.动态数据类型的高级程序设计语言. Python由Guido van Rossum于1989年底发明,第一个公开发行版发行于1991年. 像P ...

  6. fio使用

    关于lat(I/O completion latency).clat(complete latency).slat(summit latency): lat is the total latency. ...

  7. HDU 4352 XHXJ's LIS ★(数位DP)

    题意 求区间[L,R]内满足各位数构成的数列的最长上升子序列长度为K的数的个数. 思路 一开始的思路是枚举数位,最后判断LIS长度.但是这样的话需要全局数组存枚举的各位数字,同时dp数组的区间唯一性也 ...

  8. Windows 下配置Git

    在Windows上安装git很长时间了,一直都没有配置,就是简单的使用.当然配置后就可以在任意目录下使用git命令.其实就是配置下git的环境变量. 首先,当然下载windows版本:点我下载 安装直 ...

  9. ping函数

    #!/bin/bash #note:ping monitor set -u #set -x ping_fun() { d_network= echo -n "input the networ ...

  10. jquery基础 笔记三

    一. 操作"DOM属性" 在jQuery中没有包装操作"DOM属性"的函数, 因为使用javascript获取和设置"DOM属性"都很简单. ...