您的位置:首页 > 产品设计 > UI/UE

Android GUI 单线程消息队列机制 —— 多线程GUI工具箱:一个破碎的梦

2015-07-28 09:47 417 查看
前言:Android、Swing、MFC等的GUI库都采用了单线程消息队列机制来处理绘制界面、事件响应等消息《GUI为什么不设计多线程》,文章中提到了负责Swing开发的一个大师的一篇博客《Multithreaded toolkits: A failed dream?》,博客使作者了解到开发多线程的GUI toolkits是一件吃力不讨好的事,不仅开发难度大Bug多多,用起来也未必可以获得理想中的效果,其中的死锁和竞争,大师们也感到头疼。因此,我翻译了大师的博客,希望对自己和小伙伴们了解和学习GUI的单线程消息队列机制有一定的帮助。

破碎的梦

在计算机科学中,我把许多想法视为“破碎的梦”(借用弗诺·文奇的观点)。这些破碎的梦看起来是一些非常好的想法,所以它们时常地被人们提起,人们也花费了许多精力和思考在上面。这些想法是典型的研究领域的问题,同时它们也赋予应用领域一些问题有趣的属性。除非你从来感受不到这种纠结…

对我而言,多线程的GUI工具箱就是这些破碎的梦中的一个。看起来GUI工具箱理所应当采用多线程的解决方案。任意一个线程都应当能够更新GUI的button,text等等。Damned straight(作者是在爆粗么o( ̄ヘ ̄o#))…只需要一些死锁,为什么就这么难?好吧,还有一些bug,但是我们可以修复它们不是吗?不幸的是,多线程GUI工具箱实际上并没有那么简单….

从观测的结果看,在多线程GUI中,死锁和竞争会导致惊人的趋势。我第一次听到这种奇闻轶事是在上世纪八十年代,从一位在施乐帕克研究中心Cedar GUI libraries部门工作的人口中。那里有一群真正理解线程运作的非常聪明的人,所以他们对于GUI代码中有规律的死锁(导致的趋势)的断言是非常有趣的。或许他们的数据有些瑕疵或者是在一些异常的环境下吧。

不幸的是,近些年研究出现了越来越多的一致性。人们往往从研究多线程开始,逐渐转向事件队列模型。“事件队列模型才是最适应GUI工作的”。

我们在开发AWT时也经历了这些。AWT最初被列为一个标准的多线程Java库。但是随着Java团队考量了AWT和人们遇到的死锁及竞争问题的经验,我们开始意识到,也许当初许下了一个无法实现的承诺。

这些分析在1997年为Swing的某个设计复审的时候达到了巅峰。当我们复审了AWT中的播放状态和考虑了整个工业经验时,我们接受了Swing团队的建议:Swing应当仅支持非常有限的多线程运作。除了一些很少的例外,所有的GUI工具箱都应当工作在事件处理线程上。随意的线程不能直接操作GUI状态。

为什么多线程如此困难?

在1995年的事件与对应的线程的一个报告中,John Qusterhout探索了在线程驱动和事件驱动编程中的一些负载问题,同时正确指出了关于为什么多线程编程如此困难而事件驱动编程相对简单的一些理由。我没有必要赞同他对于编程的所有分析,但是我的确同意他对于GUI编程的观点。

对我而言,这个关于GUI toolkits线程运行的问题出现在对输入事件的处理和抽象层的结合中。

输入事件处理的问题就是,它在大多数的GUI活动中(与应用层代码更新UI)运行方向相反。一般来说,GUI绘制操作开始于抽象库的栈顶,并且依次往下运行。如果我在应用层执行了一个对GUI对象的抽象的想法,就需要启动应用层并在其中调用顶层的GUI抽象语句,然后调用更低一层的GUI抽象语句,接着调用GUI抽象层“恐怖的肠子”,从那里进入操作系统底层。相反,输入事件处理开始于操作系统层,渐进地被派送到抽象层,直到抵达应用层代码。

现在,既然我们正在使用抽象层,我们会自然而然地分别给每一层加锁。不幸的是,我们会遇到经典的加锁次序噩梦:我们有两种不同的活动,运行于相反的次序,两者都需要加锁。所以死锁几乎是不可避免的。

这个问题最初被浅显地理解为一系列的特殊线程bug。人们最初的解决方案是通过调整加锁来解决这种bug。在bug处释放死锁,更灵活地在别处加锁。好吧,这其实是一项有趣的活动,尽力同如大海潮汐般的力量搏斗。即使是很灵活的加锁,也终会趋向不可思议的竞争(由于加锁的不完备)或者机智又纷繁复杂的死锁(由于机智又纷繁复杂的加锁)。在95-97年我们(被死锁和竞争)缠成了一捆。

注意到这个问题已经扩展到超出GUI工具箱层级,甚至出现在toolkit层和应用层之间。我们必须给所有的操作GUI层的activity简单加锁,但是这样一来,同样的问题又浮现到了更上一层。

所以结论是什么?从某种程度上来说,你必须退一步看这个问题,在一个想要“up”的线程和一个想要“down”的线程之间有一个基本的矛盾,即使你修复了一个单独的邻接点的bug,也无法修复所有情况下的这种bug。

这种结论导致Swing团队最终采用了一个被大多数GUI工具箱所采用的解决方案:将所有GUI相关操作运行在一个单一事件线程中。这意味着在某种程度上,所有的GUI活动都将变成事件驱动,而“down”线程则会成为一个新的事件。

这一解决方案有明确的作用。APP中复杂而可靠的GUI操作变得可能。Hurrah!(author好活泼!)但是这也的确使得管理长期运行的activity变得更困难。我曾写了一个短小的Swing程序,周期性地被我用来选择性地释放email文件中的大的无聊的附件,同时我还希望能够进行进度管理以终止(进程),从而能够均衡对较大activity的处理 处理GUI activity返回到事件线程。如果我有一个不可思议的多线程库,这可能会变得更加复杂,但是它有重要的底线意义:它确实工作的更加可靠了。

微妙之处

所有的事情都是黑白分明的吗?的确曾有人成功地使用了多线程工具箱吗?Yes,但是我想着只是证实了它不过是一个破碎的梦的一个特征而已。、

我相信,如果toolkit被非常谨慎地设计过后,你可以成功的进行GUI toolkit的多线程编程;如果toolkit将其加锁方法血淋淋地细节地呈现出来;如果你非常聪明,谨慎,对整个的toolkit结构有着全面的理解。如果其中之一出现了细微的错误,事情还能继续,但是将会不定时地卡住(由于死锁)或者失灵(由于竞争)。这种多线程设计方法最适合那些深谙toolkit设计的人。

不幸的是我不认为这些特点能够被普遍地商用化。你需要合作的是普通智力的程序员,开发的APP并不需要完全安全因为绝对的安全并不是直观明显的。所以作者们会很不开心,很泄气,对这个贫乏的无罪的toolkit恶语相向(就像我第一次使用AWT的时候…Sorry!)

另一个缺点就是,如果使用多事件线程,很可能会出现GUI和Java VM同时操作。

(未完待续)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: