Silverlight游戏开发并行编程学习笔记 - 1


前言:

  其实并行编程和语言并没有必然的联系,和游戏开发也没有什么必然的关系。把它们联系在一起的是我,因为我想在Silverlight游戏开发中使用并行编程来获得更好的效果。

  有并行编程经验的朋友们都知道并行编程非常困难而且艰巨,但它是在摩尔定律失去作用后的唯一希望。

  并行编程是一门科学和艺术完美结合的学科。——多么好的句子!科学与艺术。不论多么艰难,都值得付出努力。

第一节:自旋锁和争用

在互斥协议中不能获取锁的时候:自旋锁和阻塞是两种处理的方法,也可以把两者结合起来使用。

  在目前的情况下,我只能使用C#来进行开发,或许F#在并行编程方面有着更好的表现,可惜,我还没有掌握它。但在学习的过程中,似乎看到有这样一种倾向,C#尽可能的借用着F#的东西,或者说是函数编程的东西,虽然C#完全实现函数编程可能是一种灾难,但这种倾向仍然随处可见。

  (1)选择恒定的原子值类型数据:一旦它们被创建,值就是固定的,不能修改。恒定类型天然具有线程安全性的特点,多访问者可以同时访问相同的内容。实现恒定类型并不容易,很多时候需要Clone one copy,把对象分解为自然的单一的实体结构。这个值要么是默认值,要么就是新值。在.Net 里的时间日期结构就是一个很好的例子,当你改变其中任意一个数据时,实际上是一个新的数据了。2010年6月21日被修改为了2010年6月22日,你还认为是同一个日期吗?

  为了创造一个恒定类型,要确保用户没有机会来修改内部状态,从派生类修改也不行,被动的可变的改变要实现“Defensive Copy”,我理解Clone一个数据作为一个折中方案来平衡变与不变。在引用类型数组中,用户通过了另一个引用到这个存储空间上的对象(我更习惯于称之为指向这个存储空间的域,虽然我不懂C++)来修改恒定结构,为了阻止他,就做出一个一个Defensive Copy。除了这样一个Defensive Copy,还可以参考 String 和 StringBuilder。(string 一旦初始化就不能再被修改,每次的改动实际上都生成了新的字符串,因此多次的字符串操作会产生较多的内存垃圾碎片)这样一对类。在多次操作A'类(例如StringBuilder)后生成一个恒定的A类(string)。这些听起来有点复杂(想念F#了吧)但这些努力都是值得的,恒定类型更加简单,而且容易维护。不要着急写set/get,停留一下,看看有没有更好的方法。实际上,在程序开发中,简单往往比复杂更好。

  在并行编程中,所有的想当然都是危险的,我们必须要了解相当的多处理器硬件知识和编译原理。在多个变量之间,编译器有可能把各个线程的对变量的处理进行不同顺序的操作,多处理器的存储器的写操作并不一定会在操作时生效。这看起来有点奇怪?但别忘了,计算机硬件也是一门复杂的学科,记得我在学习 Mainboard 的时候,清楚地记得一句话“即便主板的发明者也不能够弄明白每一个细节”。在大多数程序中,并不要求写操作在存储器中立即生效,存在一个特殊的“写缓冲区”(也被称作存储缓冲区),只有在需要的时候才会被写入内存。写操作 --->写缓冲区---->内存。写缓冲往往会带来操作的重拍序,线程A对a的写操作在一个缓冲区中被延迟,a就有可能在线程A读了b之后才到达内存,这或许就会和你最开始的设计意图相反。或许你会考虑设置一些东西,迫使A对a的写操作强制生效,但得到的忠告是:这样做的代价是昂贵的。

  让我们回想一下总线结构(这让想起来一些大学时的噩梦,在接触到那些课本后,我基本上绝望了,My God,这实在理解不了。可为什么我现在觉得这一切似乎都非常顺理成章)。总线结构是一种典型的多处理器系统结构,通过总线的共享来进行通信,处理器和存储控制器都可以在总线上监听,但一个时刻只能有一个处理器(存储控制器)在广播。讨论会的时候,掌握麦克风的总只能是一个人。在每个处理器上都有一个高速缓存(cache),cache和处理器的关系更加亲密,在从内存读数据是,首先是检查对应的内存地址中是否存在于Cache中,如果在,则马上加载它,如果不在,则产生一个“Cache 缺失”,接着处理器在总线上广播这个地址,其他处理器监听总线,如果某个处理器在自己的Cache中发现了这个地址,这广播出来做为相应,如果都没有发现,则以内存中该地址的值作为响应。(这听起来像是在找东西。)请注意最后一句话,如果到内存中寻找这个值就不太妙了,对内存的访问要比对Cache的访问多出几个数量级的时钟周期,所以需要尽量地避免Cache 缺失。

  基于上面的论述,在设计锁的时候,应当尽量的“自旋”,在Cache中反复;而不是去反复总线。大家看到了,总线是一条单行道,在这上面的调用,会延迟所有的线程,即便这个线程没有等待一个Lock。还会发生更糟糕的事情,保有Lock Copy 的处理器会从Cache把它们丢弃,从而造成每一个处理器都 Cache 缺失,来通过总线重新获取一遍,这本来是毫无必要的,因为这个Lock没有被修改。这还不算最糟糕的,想象一下吧,一个人在台上用麦克风呼喊着,需要一杯可乐来解渴,会场中只有你手头有一听,但可惜的是,你不能够马上给他,因为:还不轮到你发言;这就像想要释放Lock的线程却被延迟着一样。

  你现在是一个混乱局面的调度者,一个交通管理员,一个疏导者。现在你需要做什么呢?可以想象,你会大声喊着:“请安静,请安静。请挤在麦克风前面的A线程先生,B线程先生...F线程先生都让一让...”,Very Good,这样的时候,挤在一起只会让情况更糟,大家稍微退退反而可以获得更多的机会。退多少呢?这时候需要发挥绅士风度了,在尝试获取这个Lock之前,先后退。如果仍然没有得到这个锁,就退的更远,因为这意味着,争用的人实在太多,而大家做出的让步又太少,所以仍然混乱,需要继续退后。

  道理就是这样,至于代码中怎么实现,大家八仙过海吧。

  我在程序开发中强调一点,就是设计的思想,在写代码之前,最好是理清自己的思路。然后在这样一个思路的指导下去实现代码。设计的思想,并不是要保证思路是正确的,而是保证有一个思路,那么即便你是错误的也知道自己错在哪里,如果是正确的,那么正确在哪里。而不是浑浑噩噩地摇骰子般地按F5。

  已经写了不少了,希望大家打开IDE,开始编写代码了。不管你是不是和我一样是Silverlight程序员,我都希望您能够有所收益。 

参考图书:

《多处理器编程的艺术》《改善C#程序的50种方法》。

注:

其实写博客的时间不长,一向懒惰,自己水平也不高,很多东西都是拾人牙慧,加入了一些自己的理解,另外就是在游戏开发中如何实现各种技术可能有些经验;这有个问题,我不知道在自己的博客中引用这些高手的东西是否妥当,虽然我肯定我不会把自己的博客有什么商业应用,但仍然有所疑虑,是不是要做更多的事情,比如给这些书的作者发个Email,有知道这方面知识的朋友,希望能有所指教。之所以开始写博生活,也是基于共享的精神,想把自己的一些心得分享出来。我不是巨人,做个垫脚石吧。

这样多线程的东西,说实在话,非常没有把握。如果您觉得错误实在太多,有害而无益,请多多指教,尽管拍砖,我会把这个停掉的。再次感谢和抱歉。

优质内容筛选与推荐>>
1、剑桥offer(11~20)
2、Ext 实现动态拼Checkbox
3、走进Linux02-网络配置
4、POJ 2485 - Highways(求最小生成树的最大权值-Kruskal算法)
5、临安两日游


长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

    阅读
    好看
    已推荐到看一看
    你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
    已取消,“好看”想法已同步删除
    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号