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

捺捺 2008-01-22 04:20:51
转了一篇孟岩的BLOG:


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的竞赛并不想让参赛者在算法上动脑筋,而是已经把串行程序实现了,要求参赛者在其基础上改成并行多线程程序。可以从竞赛站点上分别下载Linux和Windows版的串行程序实现。其中的算法基本上是上述第二种,即从细胞出发的算法。程序当中自制了一个超级简单的链表数据结构,然后用四个链表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就会方便很多。我倾向于使用后者,不过还需要学习。

这是我的是一些思考,有兴趣的朋友不妨试试做一下这个题目。
...全文
950 7 打赏 收藏 转发到动态 举报
写回复
用AI写文章
7 条回复
切换为时间正序
请发表友善的回复…
发表回复
zhoujk 2011-05-16
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 ivony 的回复:]
分区并行计算
[/Quote]
如果这个二维方格的XY轴很大的话,可以这么做,但是可能有一些问题。就是分区的边界可能会出错或出现不确定性。
例如,这个空间中有一个区域的范围内,密码较大,应该删除一些有效点(即这道题中的生物死亡),如果刚好在一个分区,则没有问题,一个一个的计算就行了。但是如果分区的边界跨过这个大密度区域,则可能导致这个区域的后面一部分在第二个线程里先计算了,然后第一个线程算到这个地方的时候,密度已经减小了,因此会导致计算的不确定性。
因此我建议使用并行运算时,不要分区,而是先将这些点进行矢量化,然后计算矢量化的值以后返回结果
向上一区 2011-05-13
  • 打赏
  • 举报
回复
单线程算法我已经实现牞,现在正在做多线程并行处理。
xuyanhe 2008-01-23
  • 打赏
  • 举报
回复
并行算法,看着有些难度啊
wshong 2008-01-23
  • 打赏
  • 举报
回复
并行不会-_-
OpenHero 2008-01-22
  • 打赏
  • 举报
回复
有些情况没考虑到 - -!
Ivony 2008-01-22
  • 打赏
  • 举报
回复
分区并行计算
denghui0815 2008-01-22
  • 打赏
  • 举报
回复
孟岩先生完全没有考虑到并行时可能出现的意外情况

因为各个线程在并行处理时肯能会同时访问到 Grid和Gridcount

在极端的情况下 会出现错误 具体的情况我很难描叙

但是简单的并行就必须对Grid和Gridcount的反问加锁 如果加锁 效率就大打折扣了

566

社区成员

发帖
与我相关
我的任务
社区描述
英特尔® 边缘计算,聚焦于边缘计算、AI、IoT等领域,为开发者提供丰富的开发资源、创新技术、解决方案与行业活动。
社区管理员
  • 英特尔技术社区
  • shere_lin
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧