谈谈多核时代将带给软件业的变革

来源:互联网 发布:马来亚战役知乎 编辑:程序博客网 时间:2024/05/02 06:57

软件工业从80年代开始经历了一场变革, 那是由PC逐步普及软件走向商业化所推动的,面向对象语言在封装程序复杂度方面表现优异,为大型程序的开发提供了一种经济可靠的途径,第一个取得成功的当属C++,随后java/.net将其推向了颠峰。

在面向对象语言相当成熟的今天,又一轮新的变革正在酝酿之中,很可能会让我们措手不及。这一轮的变革推动者是硬件体系的改变,多核和并发将成为今后的主题。多核发展的趋势不必多言,在可预计的将来我们甚至可能看到百核千核这样的CPU问世,但是我们的软件工业还没有为并发做更多的考虑,虽然之前在单核处理器上我们也大量使用多线程,但是那只是为了改善I/O效率,而并不是为并行计算准备的。只有在多核CPU上我们才能得到真正物理上的并发执行,将计算并行化将成为未来提高程序性能的最主要手段。顺便提一下,当今的图形工业走在了并行计算的最前言,如今最强劲的GPU计算能力要比CPU高出2个数量级,而时钟频率仅有CPU的1/5,关键的因素就在并行计算上。

将计算并行化本身是个相当复杂的问题,有些问题可以非常自然的并行执行,而有些则几乎不可能。如何将计算并行化这是学术界的问题,诸如Map Reduce之类都是非常有效的并行算法。软件工业所要关心的则是如何在多核时代经济可靠的应用这些并行算法构建并行程序,这对编程语言、库、工具等都提出了新的要求。事实上,目前在并发程序上编程语言所能提供的基础设施还少的可怜,像线程、锁之类的概念太过原始,还需要大量更高层抽象来构建我们的应用。

并行程序所面临的最一大难题是共享冲突,当有多个线程要同时访问一块共享资源的时候就可能产生冲突,这个时候如果不加以协调,那么会产生错误的结果。解决冲突最简单的办法是用锁,但是锁带来的问题比它能解决的问题还要多,第一个问题是锁的粒度,如果加锁的粒度过大,并行程序可能退化成为串行程序,如果锁粒度过小(诸如在对象的每个方法上加锁),则可能导致频繁加锁/解锁,会有昂贵的overhead,并且无法保证操作的原子性。其次,锁不具组合性,当我们需要加两个或以上的锁那么就难以避免的可能出现死锁问题,解决死锁问题很难,而有些死锁问题重现的几率非常小,在开发阶段难以被察觉,这严重的降低了程序的可靠性。

最二个难题是并行程序的执行顺序很难分析,这可能会导致潜在的死锁和时序问题。在串行程序里,谁先谁后执行是完全没有争议的,但是到了并行程序就不同了,多个并发任务的执行顺序存在着多种排列组合,若是任务之间的执行存在着某种先后依赖关系,则时序问题一定会出现。有时候这种依赖会以非常微秒的方式出现,对于缺乏经验的程序员来说可能完全超乎它的直觉想象。

可见,目前主流编程语言提供的并行化基础设施已经远跟不上需求了,我们需要一种更有效的编程语言和一套更高层的抽象来简化并行程序的开发,让程序员工作在一个简单的可靠的环境中,而不是像现在这样让有经验的程序员提心吊胆,没经验的程序员鲁莽的写出BUG丛生脆弱不堪的代码。我感觉趋势可能有这么几点:

第一,函数型语言在并发程序上有着先天优势,一个得益于函数调用无副作用的特性避免了共享冲突,执行顺序也更灵活可控,针对并发应用的编程语言Erlang正是充分的利用了函数型语言的这一优势。第二个优势是我们在函数型语言上已经构建出了高度灵活抽象的数据结构和算法(诸如stream,map等),这有助于我们在更高层次上抽象并行算法,写出简洁、灵活、高效的代码。事实上,我们在许多脚本语言(如Python,ruby)上已经充分享受了函数型语言的简洁性。

第二,基于虚拟机执行的编程绪言在多核时代会变得更重要,由于并发程序内在的复杂性使得我们需要一个可靠受控的执行环境,在虚拟机环境中可以帮助我们模拟和分析并发程序的行为,并且可以有效的检测并限制程序员做一些具有潜在危险性的操作。


第三,在抽象方面,锁和线程应该被更高级的抽象取代。例如,当要执行一组并发任务时,不需要手工创建线程,而是像某个队列投递一系列异步消息,并等待这些任务全部执行完;而锁则可以移植数据库中事务的概念,将内存中的共享对象抽象为事务对象,对共享对象的操作被分解为一系列原子操作。将来这些高层的基础设施将作为库或者语言的一部分得到支持,而无需再让程序员费心。

暂时就写这么多吧,下回有时间我会介绍两个在工作中碰到的具体的并发程序工程,来探讨具体的并发程序设计问题。

 

原创粉丝点击