并发模式

让我们先来回顾一下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. 【转载】JExcelApi(JXL)学习笔记

    在公司的项目中,有excel生成.导出的需求,因此学习了用JXL读写excel,做个简单的笔记,以供参考.      实现用java操作excel的工具,一般用的有两个:一个是JXL,另一个是apac ...

  2. Redis windows主从服务配置

    一.下载redis解压 如图: 二.复制redis.windows.conf 文件为 redis.windows_6380.conf 三.修改配置IP和端口 四.配置从属于主服务的IP 和 端口 五. ...

  3. spring-security权限控制详解

    在本例中,主要讲解spring-boot与spring-security的集成,实现方式为: 将用户.权限.资源(url)采用数据库存储 自定义过滤器,代替原有的 FilterSecurityInte ...

  4. c# 过滤HTML代码 源代码,案例 下载

    #region 过滤HTML代码 //替换掉html字符,只显示文字信息. public string replaceHtmlCode(string Htmlstring) { Htmlstring ...

  5. POJ - 3169 差分约束

    题意:n头牛,按照编号从左到右排列,两头牛可能在一起,接着有一些关系表示第a头牛与第b头牛相隔最多与最少的距离,最后求出第一头牛与最后一头牛的最大距离是多少,如         果最大距离无限大则输出 ...

  6. gTest&gMock learning

    在C++中,编写服务后的一种测试方式是使用google的gTest和gMock结合 之前写py,测试方式是将服务挂起,使用工具模拟请求发包,check resp,这样的缺点在于不方便,即使存下了所有的 ...

  7. Runtime.getRuntime.exec();

    杀死Chrome浏览器进程 private static void closeAllChrome() throws IOException{ Runtime.getRuntime().exec(&qu ...

  8. CSS3:@font-face规则

    前言 过去,Web设计师为了保证网站能够正常显示,只能使用“Web安全字体”,即每台机器都预装的字体.但Web安全字体有时并不好看... @font-face能够使得任何一台机器能够显示理想中的字体. ...

  9. DataContext的在控件树上的传递

    控件树,在树上的每一个分支,包括叶子(比如:grid,stackpanel,lable,TextBlock)等,都有DataContext属性,并且该值可以实现从“外层”向内层传递 <Grid ...

  10. bind、delegate、on的区别

    on(type,[data],fn) on有三个参数,type代表事件类型,可以为“click"."onchange"."mouseover" dat ...