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\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 并发模式的更多相关文章
- Go语言-并发模式-资源池实例(pool)
Go语言并发模式 利用goroutine和channel进行go的并发模式,实现一个资源池实例(<Go语言实战>书中实例稍作修改) 资源池可以存储一定数量的资源,用户程序从资源池获取资源进 ...
- 《Go语言实战》摘录:7.2 并发模式 - pool
7.2 并发模式 - pool
- 《Go语言实战》摘录:7.3 并发模式 - work
7.3 并发模式 - work
- 《Go语言实战》摘录:7.1 并发模式 - runner
7.1 并发模式 - runner
- go语言之并发
简介 多核处理器越来越普及,那有没有一种简单的办法,能够让我们写的软件释放多核的威力?答案是:Yes.随着Golang, Erlang, Scale等为并发设计的程序语言的兴起,新 ...
- 探索 Java 同步机制[Monitor Object 并发模式在 Java 同步机制中的实现]
探索 Java 同步机制[Monitor Object 并发模式在 Java 同步机制中的实现] https://www.ibm.com/developerworks/cn/java/j-lo-syn ...
- Go并发模式:管道与取消
关键字:Go语言,管道,取消机制,并发,sync.WaitGroup,包引用,通道,defer,select GO并发模式:管道与取消 简介 Go的并发能力可以使构建一个流数据管道变得非常容易,并且可 ...
- 16 Go Concurrency Patterns: Timing out, moving on GO并发模式: 超时, 继续前进
Go Concurrency Patterns: Timing out, moving on GO并发模式: 超时, 继续前进 23 September 2010 Concurrent progra ...
- [转] Go 的并发模式:Context
[转] Go 的并发模式:Context tips:昨天看了飞雪无情的关于 Context 的文章,对 go 中 Context 有了一个初步的认识.今天看到一个 go 官方博客的关于 Context ...
随机推荐
- RDB
在运行情况下, Redis 以数据结构的形式将数据维持在内存中, 为了让这些数据在 Redis 重启之后仍然可用, Redis 分别提供了 RDB 和 AOF 两种持久化模式. 在 Redis 运行时 ...
- UVA 814 The Letter Carrier's Rounds(JAVA基础map)
题解:就是按照题目模拟就好 但是这个题目让我发现了我Java里面许多问题 具体看代码,但是还是分为这几个方面 属性的作用域问题,缓冲区问题,map与list映射的问题,输出多个空格不一定是/t,反转思 ...
- linux用户态与内核态
系统调用:如何中用户态切换到内核态 在linux中,系统调用是通过0x86体系结构中的软件中断实现的.这个软件中断与通常说的硬件中断不同之处在于,它是通过软件指令触发,而不是外部设备,这是程序员可以触 ...
- 源码安装git
1.安装依赖包 yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel 2.下载git源码并解压缩 wget ...
- Linux 本地调试Hadoop
将Hadoop部署后,可以使用java api进行访问,但是并不能像安装完mysql后用python自带的mysql api连接那么简单. Hadoop/share目录下有Hadoop所有的jar包, ...
- SQLite内存数据库操作类
using System; using System.Collections; using System.Collections.Generic; using System.Data; using S ...
- java开发设计六大基本原则
1.遵循单一职责原则 定义:不要存在多于一个导致类变更的原因.通俗的说,即一个类只负责一项职责.一个类只专注于做一件事: 高内聚,低耦合: 实例: 普通的手表如果有一个指针坏了,那么手表将不再转动,而 ...
- 字体渲染技术(字体抗锯齿技术) -webkit-font-smoothing: antialiased;
1.-webkit-font-smoothing控制的字体渲染只对MacOS的webkit有效.所以,你在MacOS测试环境下面设置-webkit-font-smoothing时,只要你不把它设置为n ...
- C4 文件和目录:APUE 笔记
C4: 文件和目录 本章主要讨论stat函数及其返回信息,通过修改stat结构字段,了解文件属性. struct stat结构定义如下: struct stat { __dev_t st_dev; / ...
- raid write back / write throught
RAID write back指的是raid控制器能够将写入的数据写入自己的缓存中,并把它们安排到后续再执行,这样做的好处就是不需要等实际写入磁盘再返回,因此写入更快.对于数据库而言,这一点更为重要, ...