Apple版 - Grand Central Dispatch:来自操作系统底层的简洁与优雅【13/23 |
|
|
|
|
|
a****a 发帖数: 5763 | 1 http://bbs.weiphone.com/read.php?tid=527142
上期连载《Grand Central Dispatch:迎接挑战(连载12/23)》中我们谈到,
GCD技术能够在操作系统的层面更加合理而充分地统筹分配系统资源,从而充分挖掘多
核系统的潜能。此前,我们在《并行难题:一封19年前的挑战书(连载11/23)》中介绍
了上世纪末Be公司推出的BeOS操作系统:
引用
BeOS操作系统最鲜明的特色在于“普适多线程(pervasive multithreading)”
技术。以现在的标准来衡量,BeBox和其他运行BeOS操作系统的计算机充分利用了计算
资源。BeBox的演示令人印象深刻。66MHz双处理器计算机能够流畅地运行多个视频并在
后台播放CD中的很多音轨——与此同时,用户界面响应也保持一贯的流畅。BeOS操作系
统让很多技术狂热者大跌眼镜,他们当中的许多人坚持认为,即便是目前的许多台式机
,操作体验仍旧无法与当年的BeOS相媲美。
19年以来,无数工程技术人员呕心沥血把自己关在实验室工作室里试图逾越“
并行处理”这一难题;19年后的今天,我们有了武装了GCD技术的Snow Leopard系统。
诚然,我们可以看到GCD通过一些手段避免了当年的BeOS系统所遭遇的问题(诸如线程的
重复使用,对全局线程池(global pool of threads)的维护和管理,等等)。尽管Apple
的牛b吹得很大,我们不禁还是会产生这样一个疑问,这一切真的比BeOS所采用的多线
程技术更为优秀么?
GCD技术中渗透了一种与BeOS的“普适多线程(pervasive multithreading)”
设计理念截然相反的概念。为了获得良好的全局响应,BeOS不惜让应用程序的每一个组
件(甚至包括每一个窗口)都在其自己的线程中运行,这无疑为程序开发者增加了沉重的
负担。而GCD则采用了一种更具限制性的、分级的方式:专门分配一个主程序线程用来
处理所有用户事件,而(其他)工作线程用来在需要的时候执行特定的任务。换句话说,
GCD不需要程序开发者来思考究竟应该如何为程序分配多个并行线程(当然GCD也会在必
要的时候协助开发者进行线程的分配与优化工作)。
Mac OS X系统中,当程序对系统事件无响应时,用户会看到一个这样的转动
的“彩球”:。 诚然,这是不少Mac用户非常痛恨的东西——无论你的机器是
四核还是八核还是四百核八百核。很多开发者也深谙此理,但是为什么我们仍旧会时常
看到这个东西呢?为什么不干脆让所有程序在背景线程中执行那些冗长的任务呢?
原因是多方面的。此前我们也谈到过一些,例如我们很难确切知道究竟应该创
建多少个线程才合适,而最重要的一个原因却在于,创建一个线程并不是一件轻松的事
情。并不是说技术上有多困难,而是说,从单纯地操控程序中的现行工作,到涉足系统
层面的任务管理,其间有太多问题需要站在更为全局的角度进行思考与权衡。
然而不幸的是,事实上有相当多非常普通的任务,通常情况下程序只需很短的
时间即可完成,但是如果其间出现了一些差错,就会耗费相当长的时间。一切涉及文件
系统的工作都有可能引发操作系统底层的混乱。对于名称检索(name lookups)也是类似
的情况,通常会瞬间完成,但是当系统不紧不慢地返回结果的时候就可能使许多其他程
序出现各种各样的问题。所以,即便是相当谨慎的Mac OS X程序也可能最终扔给我们一
个恼人的“彩球”。
对于GCD,Apple告诉我们,情况不会是这个样子。来看一个例子,假设一个程
序中有一个按钮,点击时会分析当前的文档并返回一些统计结果。通常情况下这种分析
会在不到一秒钟内完成,该按钮的执行代码如下:
函数主体中的第一行用来“分析”这个文档,第二行用来更新程序的内部状态
,第三行向程序反馈该统计结果需要被更新以反映这个新状态。一切都是很平常的样式
,而且,只要这些步骤(都在主线程上运行)不花费太长的时间,一切都将正常。在用户
点击按钮之后,应用程序主线程会及时处理用户输入动作并回到主事件循环以执行接下
来的用户动作。
但是如果用户打开了一个非常大非常复杂的文档,情况就不一定是如此了。突
然间,“分析”步骤无法在1~2秒内完成,而是需要花费15~30秒——于是我们又看到了
熟悉的“彩球”。当然,程序员可能会jj歪歪,“这只是很极端的情况,通常用户不会
打开这么大这么复杂的文档。而且我实在懒得翻手册然后给这段很简单的函数添加额外
的代码了…”
可是,如果我告诉您,您可以只添加2行代码把“分析”步骤挪到后台,而所
有这些都在现有函数内部,您觉得如何?没有程序全局对象,没有线程管理,没有关联
对象,甚至没有额外的变量… 请看:
这简单的两行代码,蕴含了许多门道。所有GCD中的功能都始于dispatch_,就
是上面代码中蓝色的部分,这段代码的关键在于第二个dispatch_async()。之前我们提
到了“工作单元(units of work)”,但是并没有详细说明GCD究竟如何操控的。可是现
在看起来答案似乎应该很明显了:block(请参阅《持续完善,构建编程友好型环境(连
载10/23)》)。block允许GCD接口直接嵌入现有的代码中,而不要求任何额外的设定或
重构等。
然而,这段代码中最引人注目之处在于,当后台任务完成并反馈结果时,它是
如何检测到的。在同步代码中(synchronous code),“分析”步骤调用的代码以及用来
更新程序显示的代码仅按照既定的次序在函数内部出现。而在异步代码中(
asynchronous code),情况仍然是如此。
上述代码中,外层的dispatch_async()在全局GCD对列中放置了一个任务,该
任务是由作为第二个参数传递的block代表的,考虑到了“分析”步骤可能耗费的潜在
时间,并囊括了其他放置在该主队列上的任务(运行在主线程上的队列)。内层block中
的代码是无法在其他任何地方运行的。设想一下,如果在“分析”步骤完成之后让后台
线程向主线程发送某种“通知”,还需要添加一些代码来对此加以识别并处理,显然这
是相当繁琐的。当“分析”步骤完成之后,内层block被放置于主队列当中并在主线程
上运行,最终更新用户界面。
显然,GCD提供了一种简洁而高效的方式。对于开发或者来说,无疑这是最佳
的解决方案。
Snow Leopard中添加的所有API中,GCD对于未来的Mac OS X具有最为深远的指
向意义。异步执行以及在许多CPU之间分派工作负载(workload),从未如此简单过。
此前,第一次听说Grand Central Dispatch时,我对此持怀疑态度。在计算机
科学领域中,如何简洁而高效地解决并行计算问题,这一难题已经持续了几十年之久。
而现在Apple声称能够解决这一问题?听起来有点荒谬哈…
事实上Grand Central Dispatch并不染指这一问题,不会协助将您的工作分割
为多个独立的可执行任务——或是决定哪些可以或应当异步/并行执行。GCD所作的则更
为务实。一旦开发者意识到某项任务可以被分割并提取出来,GCD能够使其尽可能地简
单、高效地执行。
然而,FIFO序列的使用,尤其是序列化队列(serialized queue)的存在,看起
来似乎有悖于无处不在的“并行”思想。但是我们已经能够清晰地看到多线程趋势所指
引的方向,而这对广大开发者来说无疑是一个不小的挑战。
对于Grand Central Dispatch,Apple的一个口号是:“并行”海洋中的“串
行”岛屿(islands of serialization in a sea of concurrency)。而正是这些“串行
岛屿”将开发者从同步数据访问(simulaneous data access)、死锁(deadlock)等多线
程处理时经常遇到的棘手问题中解脱出来(译注:详情请参考developer.apple.com)。
GCD倡导程序员从程序中识别出那些可以在主线程上更好执行的函数,并能够轻松地在
保持子任务之间现有的次序及独立性的基础上将整个工作单元分离出来。
是的,队列与线程的执行可以是优雅而简洁的,将其整合入底层操作系统则能
够在直观上降低使用的门槛,然而真正使得GCD广受开发者欢迎的,却是构建于block的
API。正如我们评价说Time Machine是“第一种人们真正乐于使用的备份系统”,GCD有
望最终将曾经让无数人头疼不已的异步程序设计推广到所有Mac OS X开发者当中。 |
|
|
|
|
|