大家好,我叫Clyce,大家也可以叫我川川~
今天,是我们的第一节课,在本课中,我们的目标是——
要洗。。。。脑,应该使用磨砂轮的电锯切开头骨,因为磨砂轮切割产生的高温可以使血液迅速凝固,而不会飞溅的到处都。。。
不好意思拿错书了。。。
不闹了,正文开始
让我们先从编程中“状态”的概念讲起。
说到状态,让我们先来解决一个问题,小明是谁?
神马?
没错!
让我们来看:
这个是小明
。。这个是小明
。。。。这个是小明
。。。。。这个是小明
。。。。。。。这个竟然还是小明
。。。。楼主你究竟想表达什么?
。。。
大家不觉得奇怪吗,有这么多个小明。。?
没关系啊,这些都是同一个小明啊!!!
。。。
那下面这段代码呢:
xiao_ming = new People;
xiao_XXXXXXXe = 8;xiao_ming_2 = new People;
xiao_ming_XXXXe = 30; //年龄不同怎么可能是同一个对象嘛讨厌!。。。
那么小明到底是不是小明呢?
上面的图并不是小明,只是小明的一系列照片。
上面的图并不是小明,只是小明某一个时间点的样子。
上面的图并不是小明,小明包含了上面所有的图中描述的小明的集合。
上面所有的图中描述的小明的集合也绝不是小明,小明是这个集合外更大的集合,是任意时刻的小明的总和。
小明也并不是小明,任何人都可以叫做小明。
那么如果小明不是小明,小明到底是谁?
。。
。。。
不是讲编程吗!!!怎么讨论哲学问题去了!
。。
讲到编程,那么问题就变成了:
对于传统面向对象语言,对一个事物给出的模型并不能很好的指代事物本身,而是根据上下文,可能分别指代该事物、该事物在某一时刻的快照以及该事物的别名。
于是,为了解决这样一个问题,状态(state)与身份(identity)的理念产生了。
状态即是一个事物在某一特定时空条件下所呈现的样子。
身份即是这个事物本身。
简单的来说,上面的小明中,每一张照片中存储的便是小明的一个状态。
而我们说到小明时想到的,是小明的身份。
那么状态与身份的分离,在许多现代语言(尤其是函数式编程语言)中,就变成了一个要素。
这里就得提到一个东西,不可变数据(immutable data).
数据是不可变的,因为小明无论几岁都是小明。
这里可以做这样一个比对:
//C++............................................
People* grow(People* toGrow) {
++(toGrow-> age);
return toGrow;
}
;Clojure..........................................
(defn grow [to-grow] (update-in to-grow [:age] inc))
这两个代码都创建了一个叫做grow的函数,使一个人的年龄增加了一岁。
但是区别在于,C++版本的grow将这个人的年龄增加后返回这个人,其实返回的是这个人增加一岁后的状态。而原来的状态丢失了。
Clojure版本的grow返回一个年龄增加了一岁的人,而这个人调用此函数之前的自我也能够得以保留。
如果你执行下面的C++
int i;
for (i=0; i<9; i++)
grow(xiaoMing);
你将得到十年后的小明的一个状态。
如果你在Clojure中执行下面代码:
(take 10 (iterate grow xiao-ming))
你就能够得到小明生长十年的十个状态所组成的序列。
那么如果你运行
(iterate grow xiao-ming)
就会得到小明自出生起到时间尽头的每一年的状态
当然你的电脑会先死机。
于是当身份和状态分离之后我们就能够解决小明到底是谁的问题了。
无论沧海桑天,斗转星移,海枯石烂,小明依旧是小明,只是不是原来那个小明了。
。。
可是实际编程的时候又有什么用呢?
没错,大多数实际程序是不需要和哲学打交道的,所以immutable data过去并不流行。
事实上immutable data会带来一个非常严重的问题,随着运行时间的增长,程序所使用的内存会不断增长,造成大额度的浪费。(事实上这个问题已经由共享内存技术解决了一部分)
所以,在计算机内存资源宝贵的过去,immutable data这种牺牲空间满足哲学需求的行为并不受欢迎。
可是,现在计算机内存变~~大~~~了~~~呀~~~~~~~~~
。。。决不能发扬这种奢侈浪费精神!
事实上,immutable data的真正意义在与并发程序中,也就是说,在并发程序越来越受重视的年代,immutable要火了。
。。。可是,为什么呢?
我们来打个比方,假设说你是一个动漫画师笔下的二次元生物。
<---现在的你
画师用可变数据的方法作画,
也就是说每画一个你之后,把你擦掉,
画下一个你,并且以直播绘画的方式公布整个动画。
<--现在的你
不过画师作画的速度非常快,所以直播也是每秒24帧。
有一天,画师觉得自己老了,力不从心了,找了一个朋友和他一起画你的故事。
于是画师和朋友之间就会出现这样一个局面:
这样固然是不好的,不仅画不成,还可能导致更可怕的事情。。。。。
。。。。。那我们应该如何避免呢?
很简单,把带橡皮的笔放在桌子上,谁先抢到谁就画下一幅,另一个人看着。。。。
。。
当然只能有一只笔,否则如果两个人同时拿到了笔,一起在纸上画,甚至一个画的同时另一个擦,就不太好了。
这就是锁机制,这里的笔就是锁。
于是问题圆满解决了。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。吗?
我们假设画师的朋友画完之后忘了把笔放回去,一直握在手里发呆,或者他不停画下一张下一张下一张,那画师就没机会了。。。。。。。
当然更大的问题是下面的情况。。。
“画师的朋友抢到了笔,但是纸在画师那里,画师等待朋友把笔给画师,朋友等待画师把纸给朋友”
然后画师和朋友就一直互相看着等啊等,直到天荒地老。。。
然后就没有然后了。
然后画师和朋友就过上了幸福的生活。
(上面两句就是无视写冲突造成的后果)
那么画师的朋友就必须足够聪明,以至于他能够想到可以把笔先还回去。。。
当然画师的朋友也不必费心提高自己智商,找第三个人坐在旁边,发现这种情况的时候夺下朋友的笔再交给画师就行了。
。。。。。。。。难道没有更好的办法了吗??
状态与身份的分离,不可变数据以及以操作为中心的函数式编程让动漫界跨入了一个新时代!
哦,不对,是编程界。。。
immutable data就相当于,画师终于认识到,把你的故事化成本子比画在一张纸上效果好的多。
一帧一帧画成本子的话,只要快速翻页,就能播出一部完整的动画了。
那么画师和朋友如何协作呢?
由于下一帧一定和上一帧有关,所以画师在画下一帧的时候,是要参考上一帧的。这个叫读取。
<--上一帧
于是画师参考上一帧的你开始画下一帧。这个叫做修改。
<--画师笔下的下一帧
画师画完了,开心地把这一页塞进画册里面。这个叫做提交。
可是翻开画册,他发现朋友抢先一步画完了下一帧,这个叫提交碰撞。
<--朋友笔下的下一帧
于是画师很无奈,只好放弃自己的这一帧画(回滚),根据朋友的画作,开始画更后面一帧,这个叫做重试。
于是,画师和朋友又可以一起画出高质量的动画了。
当然画师和朋友如果是鬼畜动画爱好者的话,大可以完全不管谁先谁后的往里塞,那样效率更高。
同时如果画师的朋友同时还在画番外的话,就不需要因为只有一只笔而没法在画家画主线的时候画番外而苦恼了。
这种技术叫做STM,全称Software Transactional Memory。
不过这里也有个问题:
如果画师的朋友很多很多,或者画师的朋友是个生产力极高的变态,那么画家可能必须重试很多很多很多很多很多很多次。。。。
所以说,良好优化的锁的效率其实高于STM。
可是综合来讲,这种在状态与本质分离下易于实现的并发控制相对来说成本更低些。
完全地,根本地不需要考虑写冲突,死活锁等等问题。。。
不过在这里我需要申明的是,STM并非不能作用于“状态即本质”的编程方式,只是那种编程方式中使用STM会非常混乱和难以驾驭(具体的讨论我就不赘述了,大家可以查阅)
而STM并发所需的真正要素,以及这一要素的优势,将在以后放出~
================================================
由于知识的局限性,对STM等概念的了解可能并不十分准确,如有疏漏,诚望指正。
另外,我的博客是
XXXXXXXXXXXXt 欢迎大家前来拍砖~