45 | CSP模型:Golang的主力队员
下载APP
关闭
渠道合作
推荐作者
45 | CSP模型:Golang的主力队员
2019-06-11 王宝令 来自北京
《Java并发编程实战》
课程介绍
讲述:王宝令
时长08:11大小7.47M
Golang 是一门号称从语言层面支持并发的编程语言,支持并发是 Golang 一个非常重要的特性。在上一篇文章《44 | 协程:更轻量级的线程》中我们介绍过,Golang 支持协程,协程可以类比 Java 中的线程,解决并发问题的难点就在于线程(协程)之间的协作。
那 Golang 是如何解决协作问题的呢?
总的来说,Golang 提供了两种不同的方案:一种方案支持协程之间以共享内存的方式通信,Golang 提供了管程和原子类来对协程进行同步控制,这个方案与 Java 语言类似;另一种方案支持协程之间以消息传递(Message-Passing)的方式通信,本质上是要避免共享,Golang 的这个方案是基于 CSP(Communicating Sequential Processes)模型实现的。Golang 比较推荐的方案是后者。
什么是 CSP 模型
我们在《42 | Actor 模型:面向对象原生的并发模型》中介绍了 Actor 模型,Actor 模型中 Actor 之间就是不能共享内存的,彼此之间通信只能依靠消息传递的方式。Golang 实现的 CSP 模型和 Actor 模型看上去非常相似,Golang 程序员中有句格言:“不要以共享内存方式通信,要以通信方式共享内存(Don’t communicate by sharing memory, share memory by communicating)。”虽然 Golang 中协程之间,也能够以共享内存的方式通信,但是并不推荐;而推荐的以通信的方式共享内存,实际上指的就是协程之间以消息传递方式来通信。
下面我们先结合一个简单的示例,看看 Golang 中协程之间是如何以消息传递的方式实现通信的。我们示例的目标是打印从 1 累加到 100 亿的结果,如果使用单个协程来计算,大概需要 4 秒多的时间。单个协程,只能用到 CPU 中的一个核,为了提高计算性能,我们可以用多个协程来并行计算,这样就能发挥多核的优势了。
在下面的示例代码中,我们用了 4 个子协程来并行执行,这 4 个子协程分别计算[1, 25 亿]、(25 亿, 50 亿]、(50 亿, 75 亿]、(75 亿, 100 亿],最后再在主协程中汇总 4 个子协程的计算结果。主协程要汇总 4 个子协程的计算结果,势必要和 4 个子协程之间通信,Golang 中协程之间通信推荐的是使用 channel,channel 你可以形象地理解为现实世界里的管道。另外,calc() 方法的返回值是一个只能接收数据的 channel ch,它创建的子协程会把计算结果发送到这个 ch 中,而主协程也会将这个计算结果通过 ch 读取出来。
CSP 模型与生产者 - 消费者模式
你可以简单地把 Golang 实现的 CSP 模型类比为生产者 - 消费者模式,而 channel 可以类比为生产者 - 消费者模式中的阻塞队列。不过,需要注意的是 Golang 中 channel 的容量可以是 0,容量为 0 的 channel 在 Golang 中被称为无缓冲的 channel,容量大于 0 的则被称为有缓冲的 channel。
无缓冲的 channel 类似于 Java 中提供的 SynchronousQueue,主要用途是在两个协程之间做数据交换。比如上面累加器的示例代码中,calc() 方法内部创建的 channel 就是无缓冲的 channel。
而创建一个有缓冲的 channel 也很简单,在下面的示例代码中,我们创建了一个容量为 4 的 channel,同时创建了 4 个协程作为生产者、4 个协程作为消费者。
Golang 中的 channel 是语言层面支持的,所以可以使用一个左向箭头(<-)来完成向 channel 发送数据和读取数据的任务,使用上还是比较简单的。Golang 中的 channel 是支持双向传输的,所谓双向传输,指的是一个协程既可以通过它发送数据,也可以通过它接收数据。
不仅如此,Golang 中还可以将一个双向的 channel 变成一个单向的 channel,在累加器的例子中,calc() 方法中创建了一个双向 channel,但是返回的就是一个只能接收数据的单向 channel,所以主协程中只能通过它接收数据,而不能通过它发送数据,如果试图通过它发送数据,编译器会提示错误。对比之下,双向变单向的功能,如果以 SDK 方式实现,还是很困难的。
CSP 模型与 Actor 模型的区别
同样是以消息传递的方式来避免共享,那 Golang 实现的 CSP 模型和 Actor 模型有什么区别呢?
第一个最明显的区别就是:Actor 模型中没有 channel。虽然 Actor 模型中的 mailbox 和 channel 非常像,看上去都像个 FIFO 队列,但是区别还是很大的。Actor 模型中的 mailbox 对于程序员来说是“透明”的,mailbox 明确归属于一个特定的 Actor,是 Actor 模型中的内部机制;而且 Actor 之间是可以直接通信的,不需要通信中介。但 CSP 模型中的 channel 就不一样了,它对于程序员来说是“可见”的,是通信的中介,传递的消息都是直接发送到 channel 中的。
第二个区别是:Actor 模型中发送消息是非阻塞的,而 CSP 模型中是阻塞的。Golang 实现的 CSP 模型,channel 是一个阻塞队列,当阻塞队列已满的时候,向 channel 中发送数据,会导致发送消息的协程阻塞。
第三个区别则是关于消息送达的。在《42 | Actor 模型:面向对象原生的并发模型》这篇文章中,我们介绍过 Actor 模型理论上不保证消息百分百送达,而在 Golang 实现的 CSP 模型中,是能保证消息百分百送达的。不过这种百分百送达也是有代价的,那就是有可能会导致死锁。
比如,下面这段代码就存在死锁问题,在主协程中,我们创建了一个无缓冲的 channel ch,然后从 ch 中接收数据,此时主协程阻塞,main() 方法中的主协程阻塞,整个应用就阻塞了。这就是 Golang 中最简单的一种死锁。
总结
Golang 中虽然也支持传统的共享内存的协程间通信方式,但是推荐的还是使用 CSP 模型,以通信的方式共享内存。
Golang 中实现的 CSP 模型功能上还是很丰富的,例如支持 select 语句,select 语句类似于网络编程里的多路复用函数 select(),只要有一个 channel 能够发送成功或者接收到数据就可以跳出阻塞状态。鉴于篇幅原因,我就点到这里,不详细介绍那么多了。
CSP 模型是托尼·霍尔(Tony Hoare)在 1978 年提出的,不过这个模型这些年一直都在发展,其理论远比 Golang 的实现复杂得多,如果你感兴趣,可以参考霍尔写的Communicating Sequential Processes这本电子书。另外,霍尔在并发领域还有一项重要成就,那就是提出了霍尔管程模型,这个你应该很熟悉了,Java 领域解决并发问题的理论基础就是它。
Java 领域可以借助第三方的类库JCSP来支持 CSP 模型,相比 Golang 的实现,JCSP 更接近理论模型,如果你感兴趣,可以下载学习。不过需要注意的是,JCSP 并没有经过广泛的生产环境检验,所以并不建议你在生产环境中使用。
欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程。感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给更多的朋友。
分享给需要的人,Ta购买本课程,你将得18元
生成海报并分享
赞 20
提建议
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
上一篇
44 | 协程:更轻量级的线程
下一篇
结束语 | 十年之后,初心依旧
精选留言(47)
- Sunqc2019-06-11会反复看这些内容,每次看感觉都不一样,另外老师推荐的 并发编程的艺术,还有图解java多线程模式,结合这些书看加深理解。持续关注并发编程
作者回复: 感谢感谢😄
28 - zhangtnty2019-06-11王老师辛苦了👍
作者回复: 感谢😄
9 - QQ怪2019-06-11更本看不够啊,不想老师停更
作者回复: 赶紧见好就收😂😂
6 - walkingonair2019-06-11最后一篇了,结束打卡,内容很丰富,还要复习两遍6
- 叫我天才好了2019-09-06第一遍只能说让我理解了并发的处理,还是要多复习几遍。加油吧,不拼一下你永远不知道你的潜力有多大,感谢老师的分享。
作者回复: 加油👍
4 - 每天晒白牙2019-07-17追完了4
- 宁悦2020-03-28今天看完了整个系列,觉得老师讲的非常好,自己也在看图解java多线程,加深理解。感谢老师
作者回复: 感谢支持😄
3 - helloworld2019-09-25终于看完了。对Java领域的并发编程有了更深一层的认识。认真对待专栏的每一节课程,学原理,看示例程序,思考课后的问题。尤其是第一部分关于Java多线程编程相关的理论介绍是让我 收获最大的。工作中对于Java中的各种队列了解不是很多,在这个专栏中学习了。至于专栏中关于并发设计模式和案例分析两部分的讲解,感觉了解即可。专栏虽然结束了,但是对于并发的 学习没有结束,继续!展开
作者回复: 👍
3 - 知行合一2019-07-22订了十几个专栏,老师讲的是最通熟易懂的,虽然有些知识点由于自己不够熟悉还需要多看几遍,找相关的书籍多加研习。谢谢老师!真心希望以后还能学习老师的新专栏
作者回复: 过奖啦😄现在就想休息😂
3 - 党2019-07-01现在golang也很火 golang和java 以后怎么选
作者回复: 都可以选择的时候,更多看公司的技术路线,团队积累,实际上没那么纠结
3 - 马哲富2019-06-212个星期看完了,还得慢慢消化,谢谢王老师,老师辛苦了!
作者回复: 客气啦😄看的够快的👍
3 - 海涛2019-12-13学的比较晚也比较慢,准备开始刷第二遍
作者回复: 👍
2 - jery2019-08-01想问老师一个问题,问题描述:集群中有A、B两台机器,集群与另一系统C进行通信。 系统C作为服务端最大并发数为50。如何保证集群中两台机器并 发向C发起请求控制在50并发之内。 我的思考:解决方法1:A、B两台机器平分50个并发。2.建一张表,实时监控正在执行任务任务的线程数,让A、B并发总数不大于50.
作者回复: ab每台机器最多开25个线程就可以了,不知道是否有必要那么精确
共 2 条评论2 - J.M.Liu2019-06-12全程跟完的,飞机起飞关机前,打卡。感谢老师
作者回复: 感谢支持😄
2 - 一道阳光2019-06-11个人感觉把前三个模块重点学完,后面的框架、其他模型有空了解下,不是重点。对于框架部分,有阅读源码能力有必要深入研究。2
- 卢2021-10-2019年买的,21年才看,老师写的太好了,yyds共 1 条评论1
- M2021-05-12最后两章有点懵逼,不了解golang。。。1
- Geek_30c31e2020-12-27来的比较晚,哈哈哈,持续复习。1
- 爱读书的张先生2020-07-02记录一下,终于看完了,感谢王老师。
作者回复: 👍🏻
1 - kimoti2020-06-01坚持到了最后一篇,感谢王老师
作者回复: 恭喜😄
1