“不要通过共享内存来通信,而应该通过通信来共享内存”

这句话是一条编程指导建议,和golang本身关系不大。

它适用于所有多线程、多进程程序设计场合,特别是在并发框架设计中是一种非常重要的思想。作为并发编程的初学者,可以把这句话当成一个结论,先记下来,之后在实践中慢慢体会。

而这句不痛不痒的话,却是前辈们走了很多弯路、尝试了很多种通信方法才总结出的经验。

1、“内存共享”有着巨大的潜力和诱惑力

如果在一个系统中,两个线程或进程,都可以读写同一块内存空间,这就叫做“内存共享”。直觉上会觉得这种方式非常方便。

在内存共享的情景下,系统之间不需要做频繁的沟通,所有必要的信息都在内存中,想取就可以随时取。比如A和B两个游戏角色,B想查阅A的数据,就在内存中找到它,直接读取即可。

所有的数据都是最新的,没有复制备份,就不会有过期的困扰。

而且还有更多好处——比如传输超大量的数据时。假设A对象数据量很大,占100MB空间,那么B可以随时访问这100MB空间,不需要在内存中拷来拷去。只要有这100MB内存的首地址指针就可以了。拷贝数据的消耗为0。

收发消息的需求也不难做,A只要在特定的地方写下一个消息内容,B之后来读就可以了。毕竟大家的信息本质上都是共享的。

由于共享内存有这么多好处,所以人们一直没有放弃对内存共享的探索。

2、“内存共享”的天然缺陷

但是,内存共享看起来很美好,实际有着决定性的弊端。

但凡懂一点并发的人都知道——问题在于数据冲突。多线程或多进程场景下,多个对象同时访问同一块数据,几乎一定会产生数据冲突。

为了对抗数据冲突,人们发明了很多机制。比如加锁、信号量、原子锁、巧妙的多线程算法等等。但是这些算法看起来很高级,实际上要么会影响并发性能、要么对使用场景有要求、要么很烧脑很难证明正确性。

在实践中,运行效率再高,也得以正确性为前提才可以。如果因为并发影响了结果正确性,那就毫无意义;如果因为照顾正确性影响了并发性能,那不如直接写成单线程程序。

3、比“内存共享”更合理的解决方案

人们通过多年试错和迭代,最终“通过通信来实现进程/线程间交互”的方案脱颖而出,成为了大多数人的共识。

通过通信让多线程/多进程交互,有多种具体的技术方案。erlang、go等语言在语言核心层提供了相应的功能,其它语言比如c#可以通过Task和相关的库提供类似功能。游戏服务器领域的skynet(用c语言+lua编写)也从零实现了高性能的actor框架。

各种技术方案百花齐放,它们背后的思想高度相似——先提供一个或多个高性能队列,线程/进程/微服务之间需要访问别人时,不能直接读写别人的数据,而要通过队列提出请求,然后在对方处理请求时再做相应处理。

这种设计一定程度上会增加代码的复杂度,但是规范化以后还是比较容易开发的。

通过良好的设计,整个逻辑就理顺了,只要遵循框架的规范,很多对象可以同时运行、高效协作。不会再遇到互相抢数据、读写错乱的情况。

总结

以上从整体的角度解释了“内存共享”与“消息通信”的基本思想。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值