以通信方式共享内存,不要以共享内存方式通信
Posted dog250
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了以通信方式共享内存,不要以共享内存方式通信相关的知识,希望对你有一定的参考价值。
题目是一句golang编程箴言,对它的理解可大可小。
往小了说,golang建议使用channel来共享信息而不是使用共享内存,这是一种优雅的方式,避免了数据同步带来的繁琐和低效。
往大了说,本质上还是让资源去调度请求,而不是让请求去调度资源。
资源就那么多,所有请求有序使用资源的方式就是通信的方式,反过来,为每个请求虚拟出它独占资源的假象,那就是共享的方式。两种截然不同的方式,差异体现在仲裁成本,这个成本决定了它们承载并发的能力。
再看下面这篇文章:
https://zhuanlan.zhihu.com/p/492863461
一个一个说。
电路交换 vs 分组交换
- 电路交换试图占有整条电路(其实是最后一公里),若不成功,必须等到成功。
- 分组交换将长信息分割成若干小数据包,小数据包统计复用链路。
批处理系统 vs 分时系统
- 批处理用户一旦使用系统,则会独占系统到任务完成,其它用户等待。
- 分时系统将时间分片,多用户被调度复用时间片。
CSMA/CD vs 交换式以太网
- CSMA/CD主机试图独占总线发送数据包,若不成功便退避直到成功。
- 交换式以太网数据包在交换机有序排队,复用buffer。
Apache vs nginx
- Apache为每一个请求生成一个task,该task一旦获得CPU,其它task将等待。
- Nginx采用异步模型,所有请求分时复用固定数量task的CPU时间。
共享内存 vs erlang/go channel
- 共享内存对写写以及读写是互斥,每次只允许一个操作,其它不得不等待,重试。
- erlang/go channel将内容拆解为事务消息,依靠消息的有序传递共享信息。
…
我们来看上述两两比较的共性。
可将上述所有的二者抽象为争抢模式和有序模式:
- 对于争抢模式,本质上需要对冲突进行仲裁。
- 对于有序模式,本质上需要对并发进行调度。
所谓对冲突进行仲裁,意思就是发生冲突后怎么办。无论是退避重试,还是等待,此期间均是什么都做不了,且仲裁本身需要昂贵的成本。
并发调度就会好太多,有序化便无冲突,也就没有仲裁成本了,没有了仲裁,也就无需重试,等待,便可以干别的了,处理完全异步化。
以上述列举的顺序,分别是:
电路交换 vs 分组交换
- 电路交换一旦占线,你需要自己不断重试。
- 分组交换你只管发数据包,交换节点会自动调度这些数据包到达目的地后重组。
批处理系统 vs 分时系统
- 批处理系统一旦系统被占,你就要排队等待或者待会儿再来。
- 分时系统你只需要下发任务,任务调度系统会让所有用户的任务分时复用时间片。
CSMA/CD vs 交换式以太网
- CSMA/CD网卡需要不断监听冲突并重试。
- 交换式以太网卡只需要发包,交换机会排队调度来不及转发的数据包。
Apache vs Nginx
- Apache线程/进程若没被调度到CPU,就需要等待直到被调度切换至CPU。
- Nginx只需将事件通知到,工作进程便会轮询处理完所有请求。
共享内存 vs erlang/go channel
- 共享内存访问需要加锁,若持锁失败,要么忙等重试,要么待会儿再来。
- erlang/go channel以消息传递通信,消息发出后就不用管了,除非它希望得到回馈,完全异步。
可见,这又是一个殊途同归。同类的还有:
- PCI vs PCIe,从总线到交换。
- 宏内核 vs 微内核,从共享数据结构到消息传递。
- Spin/RW Lock vs RCU Lock,从争抢锁到操作副本原子更新。
为什么冲突仲裁的争抢模式无法承载大并发,因为过载的冲突仲裁开销会将资源淹没,若要承载大并发,必然要采用调度的方式。要理解这一要素,需要换一个视角。
我们看操作的是信息的本身还是信息的副本。
回到本文题目,“以通信方式共享内存”操作信息的副本, 而“以共享内存方式通信”则操作信息本身。
操作信息副本可以保证同时有且只有一个实体操作该副本,如果有两个实体需要操作该副本,那就再复制一个副本,这就保证了无冲突,业务流是可控无阻塞的。
RCU可做到业务无阻塞并发,无论是spinlock还是rwlock,都做不到。spinlock/rwlock锁临界区,造成临界区串行化,而RCU没临界区,它将本属于临界区的逻辑作为副本操作,择机原子更新,这便可做到无阻塞并发。
操作副本是无阻塞并发的甘泉,如果把并发看作是时间扩展性,那么将信息共享到远方则是空间扩展性,完成这件事的是网络,目前它是TCP/IP网络。TCP/IP网络采用了“以通信方式共享内存”的方式,它无疑是正确的。
我不懂erlang,但大致知道它的意思,erlang没有变量,只操作副本,它是通信网络在编程语言上的映射,对于golang,大概也是如此,使用go channel可以像网络收发一样来处理信息。
我们看socket接口,它实属用通信的方式共享内存的古老方式。
socket接口一开始是进程间通信机制,与之通信的进程可在本机,也可在远处,可在世界任意地方。“以通信方式共享内存“,是最原始的编程模式,一直到现在依然正确。
共享内存是一种本地优化,仅有编程意义,却没有扩展性,无论是无阻塞并发的时间扩展性,还是将信息传递给远方的空间扩展性。
共享内存是一种本地优化,优化的是指令操作延时,与其将信息封装成消息并传递,不如直接操作信息本身,它编程更简单,代码指令更少,执行延时更低。但高并发并不care指令延时,高并发care同时执行的有效指令数,而spin,switch不属于有效指令,故共享内存天生不与高并发配对。
此外,还是那个观点,网络编程场景,普遍毫秒级的单流通信延时,共享内存相比消息传递节省个微妙甚至纳秒级的操作延时,并无太大意义。要怪就怪光速吧。
上周周中,一个经理提到了“以通信方式共享内存,不要以共享内存方式通信”这句话,周末写篇杂感。
浙江温州皮鞋湿,下雨进水不会胖。
以上是关于以通信方式共享内存,不要以共享内存方式通信的主要内容,如果未能解决你的问题,请参考以下文章