Confinement

该模式用于处理数据限制问题,类似于生产者和消费者模式。使用channel的方式通过共享信息的方式进行。有一个协程专门负责生产,另外一个协程负责接收数据。代码中使用随机的时间模拟实际情况中耗时部分。

package mainimport ("fmt""math/rand""time"
)func main() {chanOwner := func(n int) <-chan int {results := make(chan int, 5)go func() {defer close(results)for i := 0; i < n; i++ {results <- ifmt.Printf("produce %d\n", i)t := rand.Intn(2000)time.Sleep(time.Duration(t) * time.Millisecond) // 随机睡眠0-2秒}}()return results}consumer := func(results <-chan int) {for result := range results {t := rand.Intn(2000)time.Sleep(time.Duration(t) * time.Millisecond) // 随机睡眠0-2秒fmt.Printf("Received %d\n", result)}fmt.Println("Done receiving")}results := chanOwner(5)consumer(results)
}
/*
代码输出:
produce 0
produce 1
Received 0
produce 2
Received 1
produce 3
produce 4
Received 2
Received 3
Received 4
Done receiving
*/

这种模式可以根据实际情况定义生产和消费的方式,不用担心出现数据竞争的问题。

for-select循环

最常规的模式:

for { // 死循环select {// 在这里采取有关操作}
}

通过迭代的方式把数据写入channel

for _, s := range []string{"a", "b", "c"} {select {case <-done:  // 这里是完成条件的标记returncase stringStream <- s:  // 在这里写入数据}
}

死循环等待结束标记。
这种方式会尽可能早的结束工作,只要done信号到达,立刻终止:

for {select {case <-done:returndefault:}// do something here
}

另一个等价方式

for {select {case <-done:returndefault:// do something here}
}

防止goroutine泄露

尽管goroutine是一种轻量级的进程,而且一般不必担心使用太多的协程导致内存的问题,Go语言的有自动回收机制;但是,在某些情况下确实需要考虑出现某些协程一直无法回收的问题,这可还会引发一些其它的不良后果,给出下面的例子:

doWork := func(strings <-chan string) <-chan interface{} {completed := make(chan interface{})go func() {defer fmt.Println("doWork exited")defer close(completed)for s := range strings {// Do somethingfmt.Println(s)}}()return completed
}
doWork(nil)
fmt.Println("Done")

在上述的代码中,doWork会永远阻塞,因为空的string channel不会有任何内容输出。本例子中的开销可能很小,但是在实际的工程中,这可能会引发大的问题。解决方法是通过父协程结束子协程。通过父协程给子协程发射终止的信号,使得子协程自动终止。

给出一般的操作方式:

   doWork := func(done <-chan interface{},  // 终止的信号strings <-chan string,    // 等待读取的字符串) <-chan interface{} {        // 子协程返回自己终止的信号terminated := make(chan interface{})go func() {defer fmt.Println("doWork exited")defer close(terminated)for {select {case s := <-strings:// do somethingfmt.Println(s)case <-done:  // 如果关闭,则直接执行returnreturn}}}()return terminated}done := make(chan interface{})terminated := doWork(done, nil)  // 接收子协程的终止信号go func() {// cancel the operation after 1 secondtime.Sleep(time.Second)fmt.Println("Canceling doWork goroutine...")close(done)  // 关闭后相当于不存在阻塞的情况了。。。}()<-terminated  // 在这里等待子协程的终止fmt.Println("Done.")

上述代码是读数据的例子,下面给出写数据时发生协程泄露的例子:

package mainimport ("fmt""math/rand"
)func main() {newRandStream := func() <-chan int {randStream := make(chan int)go func() {defer fmt.Println("newRandStream closure exited.")defer close(randStream)for {randStream <- rand.Int()}}()return randStream}randStream := newRandStream()n := 3fmt.Printf("%d random ints:\n", n)for i := 0; i < n; i++ {fmt.Printf("%d: %d\n", i, <-randStream)  // 注意这种使用方式,也是合法的}
}
/*
输出结果:
3 random ints:
0: 5577006791947779410
1: 8674665223082153551
2: 6129484611666145821
*/

上述代码中,randStream始终没有结束,出现了协程泄露。。

改进方案:和写数据的方式类似,通过父协程给子协程发射结束信号即可。代码方案:

package mainimport ("fmt""math/rand""time"
)func main() {newRandStream := func(done <-chan interface{}) <-chan int {randStream := make(chan int)go func() {defer fmt.Println("newRandStream closure exited...")defer close(randStream)for {select {case randStream <- rand.Int():case <-done:return}}}()return randStream}done := make(chan interface{})randStream := newRandStream(done)n := 3fmt.Printf("%d random ints\n", n)for i := 0; i < 3; i++ {fmt.Printf("%d:%d\n", n, <-randStream)}close(done)// 等待同步看效果,不用等待也可以正常结束的,这里仅仅是为了显式说明一下time.Sleep(time.Second)
}
/*
输出结果:
3 random ints
0:5577006791947779410
1:8674665223082153551
2:6129484611666145821
newRandStream closure exited...
*/

or-channel方式

这种模式的方式是:把多个channeldone连接到一个done上,如果这些channel中任何至少一个关闭,则关闭这个done。 代码中,如果出现一个任意一个协程结束,那么就出现终止信号。终止信号出现后,如果有协程没有结束,他们会继续执行,代码只是检测是否有协程终止,而不主动结束协程。
给出实例代码:

package mainimport ("fmt""time"
)func main() {// 从这里传入各个channel的donevar or func(channels ...<-chan interface{}) <-chan interface{}or = func(channels ...<-chan interface{}) <-chan interface{} {switch (len(channels)) {case 0: // 递归结束的条件return nilcase 1: // 只有一个直接返回return channels[0]}orDone := make(chan interface{}) // 这是自己的标记// 可以理解成一棵协程树,父节点需要孩子节点结束才能销毁。。。// 在这里进行协程孩子节点的拓展,WTF好难理解。。。。。// 在匿名函数中,如果一个channel的任何一个子channel结束,那么匿名函数的阻塞就会立刻结束,// 之后会执行内部的defer操作,然后return一个关闭了的channel,相当于解除阻塞go func() {defer close(orDone) // 结束的时候释放本身的done信号switch len(channels) {case 2:select {case <-channels[0]:case <-channels[1]:}default:select {// 如果case失败,则进行default,需要再判断一下,防止此次突然有结束的信号了case <-channels[0]:case <-channels[1]:case <-channels[2]:// 在这里追加父节点的协程终止信号,因为这是一棵或的树,只要有一个节点成功就可以释放掉// 因此把父节点一起传入,只要有一个释放掉,父节点的channel就立刻进行释放......好机智的操作// 这里追加自己的orDone,是为了`case <-or(append(channels[3:], orDone)...): // 注意使用...符号}}}()return orDone}sig := func(after time.Duration) <-chan interface{} {c := make(chan interface{})  go func() {defer close(c) // 所在的goroutine结束后close,使用时间模拟工作时间time.Sleep(after)}()return c}start := time.Now()<-or(sig(2*time.Hour),sig(5*time.Minute),sig(1*time.Second),sig(1*time.Hour),sig(1*time.Minute),)fmt.Printf("done after %v\n", time.Since(start))
}
/*
输出结果:
done after 1.000182106s
*/

从上述看出,仅仅执行到结果最短的那个,相当于一个“或”操作。代码采用了尾递归的方式,因为select方式无法预判channel的数量,而循环的方式需要处理大量的阻塞问题,不如尾递归的方式简洁。

上述代码最后的递归中,有一个地方不太理解:代码递归的过程中为什么要加入orDone?希望有明白的同学可以解释一下!

Go语言常用的并发模式(上)相关推荐

  1. 通关GO语言11 并发模式:Go 语言中即学即用的高效并发模式

    上节课我为你讲解了如何通过 Context 更好地控制多个协程,课程最后的思考题是:如何通过 Context 实现日志跟踪? 要想跟踪一个用户的请求,必须有一个唯一的 ID 来标识这次请求调用了哪些函 ...

  2. Go语言中常见的并发模式

    Go语言最吸引人的地方是它内建的并发支持.Go语言并发体系的理论是C.A.R Hoare在1978年提出的通信顺序进程(Communicating Sequential Process,CSP).CS ...

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

    7.2 并发模式 - pool 转载于:https://www.cnblogs.com/52php/p/6722154.html

  4. 不同性能测试工具的并发模式

    大家所熟悉的性能测试工具有Loadrunner.JMeter,以及其他小众一些的工具,如Locust.Ngrinder.Gatling等等,那么你们知道这些工具有什么不同吗?为什么有的工具能模拟数千上 ...

  5. 厚积薄发打卡Day75 :【MSUP】Java语言特性与设计模式(上)

    前言 在看狂神频道的时候偶然发现下图,感触颇深.特别在当今[程序 = 业务 + 框架]思想盛行的开发者中,夯实基础基础显得格外重要,因此开此专栏总结记录. 设计模式详解 设计模式的考察点,一般有2个: ...

  6. [WCF编程]13.并发:服务并发模式

    一.概述 传入的客户端调用消息会分发给Windows I/O线程池(线程默认为1000)上的服务实例.多个客户端可以发起多个并发的调用,并且服务可以在多个线程上处理这些请求.如果传入的调用分发给同一个 ...

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

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

  8. c语言常用术语,保证让你大开眼界

    c语言常用术语 术语一:预备知识 术语三 :c语言基础 术语:表达式与运算符 术语:函数 术语:指针 术语:位运算 术语:存储管理 术语:文件 术语一:预备知识 机器语言:机器语言是直接用二进制代码指 ...

  9. Golang的并发模式

    简介 主要学习3个在工程中常用的并发包,同时学习这些包常用的情景模式. runner runner可以给一组任务进行顺序分配,然后进行总体的时间限制.该方式确定了任务的顺序后,可以让任务顺序执行,直到 ...

最新文章

  1. 与旷视、商汤等上百家企业同台竞技?AI Top 30+案例评选等你来秀!
  2. android 魅族扫码,魅族Flyme8扫码快传太实用,轻松实现文件高速传输
  3. linux 三大利器 grep sed awk sed
  4. Docker Overlay 介绍
  5. python与办公结合_python在办公时能给我们带来什么?
  6. 4高并发服务器:UDP局域网服务器(组播)
  7. NVIDIA DIGITS 5.1-dev学习笔记之安装过程记录:Windows10 x64位系统 、 MicroSoft Caffe Master、CUDA 8.0 、Python 2.7
  8. maven配置testng_TestNG和Maven配置指南
  9. 软件工程-第二次作业-例行报告
  10. mysql float 怎么设置长度_MySQL中float double decimal区别总结
  11. angularjs的基础知识
  12. Linux mail 邮件发送
  13. android 图片墙拼贴,三步搞定 用APP打造图片文字拼贴效果
  14. c语言1117查找数组元素,路雪军 Carl
  15. Android开发 点滴
  16. 「硬核JS」一次搞懂JS运行机制
  17. 分辨率不低于300dpi怎么调?如何快速修改图片分辨率?
  18. 所有方向你要的资料干货这都有,从入门到实战!【CSDN宝藏资料图鉴第一期】
  19. python绘制堆叠图_Python Pandas:绘制100%堆叠图形issu
  20. 常用电平LVTTL、LVCMOS、LVDS、CML的标准和区别

热门文章

  1. 牛客寒假算法基础训练营5
  2. Dell R730 服务器重装系统Ubuntu16.04
  3. ESP32开源驱动库Easyio的使用,基于ESP-IDF开发框架,非Arduino
  4. 【Qt串口调试助手】1.2 - 串口数据接收不发生换行,CH340 / CP2102 多硬件兼容
  5. MQTT 控制报文 - PUBLISH发布消息,PUBACK,PUBREC,PUBREL,PUBCOMP - 第6章
  6. JS助记 ----- 正则表达式
  7. xml TO json(非递归实现)
  8. 【设计模式】设计模式六大原则
  9. IT职场人生系列之十:创业观
  10. 星尘小组第十一周翻译-设计和优化索引