“生命游戏”的多线程算法思考

Intel正在ISN网站上举办一个 多线程编程大赛,值得关注。Intel过去几年举办过好几次线程技术大赛,包括与topcoder合作的一些竞赛,质量都不错。题目难度适中,而且具有启发性,对多核编程感兴趣的C/C++程序员应该关注一下。其实参与这样的活动,置身于竞赛气氛当中,无论是否获奖,都可以在短时间内大幅度地提高对多线程编程的理解。这次比赛比较有特色,为期长达几个月之久,而且每个月都有一轮竞赛,每月评选一轮优胜奖,奖品也很诱人,是一颗4核的酷睿2CPU ;-)

本月(2008年1月)的题目是一个经典问题“生命游戏”。这是英国数学家John Conway发明的一个有趣的游戏。不过这个游戏之所以名声大噪,还得归功于著名科普作家马丁•伽德纳。他在1970年10月号的《科学美国人》杂志“数学游戏”专栏介绍了生命游戏,不但让大众迷上这个游戏,也令多专业数学家产生了研究的兴趣,甚至产生了一个新的数学研究领域cellular automata(细胞自动机?),听说这个领域还对模拟类游戏产生了影响,不知是否确有其事。

大致来说,生命游戏是这样玩的。在一个由正方形小格子组成的二维网格(就像国际象棋棋盘那样)里,生活着一群细胞。每一个细胞占据一个小格子。它的四邻左右(最多)有8个格子,如果那些格子里也生活着细胞,那么这些细胞就成为“邻居”。

细胞对生存环境要求苛刻,太孤独的话会死。但是食物有限,所以太拥挤也会死。细胞还需要繁衍生息,如果邻居数量合适,就可以在空格子里分裂出新的细胞。数学家们研究的话题是,怎样制定规则才能让细胞群的繁衍发展呈现某种特定的模式,比如说,能够稳定持续地发展下去,或者逐渐消亡,或者恒定不变,或者,最有趣的是,在几个状态之内反复循环。要想“生生不息”,这些条件即不能太严苛,也不能太宽松。总之,Conway对于生命游戏制定的规则如下:

1. 如果一个细胞只有0或1个邻居,它将因为孤独而死;
2. 如果一个细胞有4到8个邻居,它将因为拥挤而死;
3. 如果一个细胞恰有2或者3个邻居,它将继续生存下去;
4. 如果一个空格子恰有3个邻居,将“生”出一个新细胞;
5. 其他的空格子继续维持原状。

这个问题出现在程序员面前的时候,大多数是要求开发一个程序来对这个游戏进行模拟。我以前学习数据结构和算法的时候曾经接触过,但是没有深入思考。其实这个问题很有趣,算法上可以有些变化,但主要是数据结构的设计,可以有几种不同的考虑。比如可以从网格角度出发,设计一个(可能是稀疏的)矩阵,用0或1表示细胞的生死,每过一代就对矩阵进行一次全局扫描,决定细胞的生死。也可以从细胞出发,把每个细胞的二维坐标位置记下,然后一个细胞一个细胞地考察,没考察一个细胞,就把它可能影响到的其他细胞或者空格纳入视线,等到全部细胞考察完毕,也就可以一次性决定下一代的格局。后面这种算法减少了需要考虑的情况数量,当网格很大而细胞比较稀疏时就节省了时间。

不过这次Intel的竞赛并不想让参赛者在算法上动脑筋,而是已经把串行程序实现了,要求参赛者在其基础上改成并行多线程程序。可以从竞赛站点上分别 下载LinuxWindows版的串行程序实现。其中的算法基本上是上述第二种,即从细胞出发的算法。程序当中自制了一个超级简单的链表数据结构,然后用四个链表newlive, newdie, maylive, maydie,分别表示新生的,刚死的,可能出生的和可能死掉的细胞。然后用TraverseList函数遍历链表,并对链表中的每一个元素施加相应的操作。最后把结果一口气写在一个5002x5002的网格中。实际上这个网格的有效尺度是5000x5000,多出来的那两行和两列,是为了方便边界条件的处理。整个算法还是很直白的,建议有兴趣的人直接下载程序来阅读理解。

我大致思考了一下,实际上这个题目还是属于一个数据并行的算法,关键在与把链表的访问函数并行化,包括ClearList,TraverseList,CopyList,如果能够充分并行化,则可以利用多核CPU的多个硬件线程加速程序的执行。如果是为了这个目标,简单的单链表就似乎不是最好的数据结构,而类似STL中std::vector那样的动态数组就比较适合,因为可以很有效的分段。我的大致想法如下:

用std::vector取代List作为基本数据结构,设定一个合适的grain size,也就是说一个线程处理的细胞数量。一般来说,这个数量的大小大致以需要消耗10,000条机器指令为好,我估计在这个例子中,100是一个合理的数值。然后根据现有细胞数量,确定线程数N。主线程创建N个线程后调用join阻塞自己,等待那N个线程分别在List的不同片段上执行相同的操作。当所有的线程执行完毕之后,就可以得到所需的结果。这里还应该用线程池来避免频繁地创建线程。至于算法,直接用程序中提供的即可。这个题目主要还是考察线程操作。

这里的难点还是在那些底层的线程API。我不太熟悉pthread,对Windows线程还是比较熟悉的。尽管如此,让我去写一个小的线程池,然后再把每一个片段任务分配给线程来执行,再处理同步什么的,还是有点麻烦。直接使用OpenMP或者 Intel Threading Building Blocks就会方便很多。我倾向于使用后者,不过还需要学习。

这是我的是一些思考,有兴趣的朋友不妨试试做一下这个题目。

Intel线程挑战赛的网址是:
http://softwarecontests-zho.intel.com/threadingchallenge/

在网页下方可以看到这个题目的详细信息,并且可以下载串行范例代码。








你可能感兴趣的:(多线程,游戏,算法,大众硬件,网页游戏)