有关GUI应用程序中线程数的最佳实践
过去,我曾与许多专门从事GUI应用程序编写工作的程序员合作。
而且给我的印象是,它们几乎普遍地在其应用程序中最小化了多线程的使用。在某些情况下,它们似乎已竭尽全力以确保使用单个线程。
这很常见吗?这是GUI应用程序设计的普遍接受的哲学吗?
如果是这样,为什么呢?
[编辑]
有许多答案表明应尽量减少线程使用以降低复杂性。通常,降低复杂度是一件好事。
但是,如果我们查看对外部事件的响应至关重要的任何数量的应用程序(例如,Web服务器,任何数量的嵌入式应用程序),则在使用线程的态度上似乎存在很大差异。
解决方案
我认为就Windows而言,由于Windows消息泵的工作方式,我们仅限于在单个线程上进行的所有GUI操作,为了提高响应速度,大多数应用程序至少添加一个额外的工作线程来执行更长的运行任务,否则这些任务会阻塞并导致用户界面无响应。
线程从根本上讲很难,因此用多个线程或者多个线程来思考通常会导致大量调试工作,现在有一句名言使我无所适从:"如果我们认为自己了解线程,那么我们就真的不知道"
GUI通常不使用很多线程,但是它们通常会抛弃另一个线程来与某些子系统进行交互,尤其是当那些系统花费一些时间或者资源非常共享时。
例如,如果要打印,我们经常会想甩掉另一个线程来与打印机池进行交互,因为它可能在一段时间内很忙,因此没有理由不继续工作。
另一个示例是与SQL Server交互的数据库负载或者类似的负载,由于所涉及的延迟,我们可能想要创建另一个线程,以便主UI处理线程可以继续响应命令。
大多数GUI框架都不是线程安全的,这意味着所有控件都必须从创建它们的同一线程访问。仍然,创建工作线程以具有响应性应用程序是一个好习惯,但是我们需要小心将GUI更新委派给GUI线程。
我们在应用程序中拥有的线程越多,(通常)解决方案越复杂。通过尝试最小化GUI中使用的线程数,可以减少潜在的问题区域。
另一个问题是GUI设计中的最大问题:人。人类因无法同时做多件事而臭名昭著。用户习惯快速点击多个按钮/控件,以尝试更快地完成某些操作。计算机通常无法跟上此要求(这仅由GUI使用多个线程跟上来的明显能力所弥补),因此,为了最大程度地减少这种影响,GUI将在单个线程上以先到先得的方式响应输入。这样,GUI被迫等待,直到系统资源释放为止,直到它可以继续运行为止。因此,消除了可能出现的所有令人讨厌的死锁情况。显然,如果程序逻辑和GUI位于不同的线程上,那么这将超出范围。
从个人喜好出发,我倾向于使事情在一个线程上保持简单,但又不损害GUI的响应能力。如果一项任务花费的时间太长,那么我会使用其他线程,否则我只会坚持一个线程。
我见过同样的事情。理想情况下,我们应该在后台线程中执行将花费比数百毫秒更长的时间的任何操作。任何超过100毫秒的分类器和人类都可能不会注意到差异。
我过去使用过的许多GUI程序员都害怕线程,因为它们"很难"。在某些GUI框架(例如Delphi VCL)中,存在关于从多个线程使用VCL的警告,这往往使某些人感到恐惧(其他人则将其视为挑战;))
BeOS API是多线程GUI编码的一个有趣示例。应用程序中的每个窗口都有其自己的线程。根据我的经验,这使BeOS应用程序感觉更灵敏,但确实使编程变得有些棘手。幸运的是,由于BeOS默认情况下被设计为多线程的,因此API中有很多东西可以使事情比我所使用的其他OS更加容易。
一般来说,GUI框架不是线程安全的。对于诸如Swing(Java的GUI API)之类的事情,只能有一个线程在更新UI(否则可能会发生不好的事情)。只有一个线程处理调度事件。如果有多个线程更新屏幕,则可能会出现一些难看的闪烁和错误的图形。
但是,这并不意味着应用程序需要是单线程的。当然,在某些情况下,我们不希望这样。如果单击计算pi到1000位数的按钮,则不希望UI锁定并且在接下来的几天内不希望按下该按钮。这是诸如SwingWorker之类的东西派上用场的时候。它包含两个部分:一个在单独的线程中运行的doInBackground()和一个在doInBackground线程完成后的某个时间处理用于更新UI的线程调用的done()。这样可以快速处理事件,或者在后台处理需要较长时间的事件,同时仍然让单线程更新屏幕。
如先前的评论所述,GUI框架(至少在Windows上是)是单线程的,因此是单线程的。另一个建议(在实践中很难编写代码)是将线程数限制为计算机上可用内核的数量。CPU一次只能用一个内核执行一项操作。如果有两个线程,则必须在某个时刻进行上下文切换。如果线程太多,则计算机有时可能花费更多的时间在线程之间交换而不是让线程工作。
随着摩尔定律随着更多内核的变化而变化,并且编程框架有望发展,以帮助我们更有效地使用线程,具体取决于程序可用的内核数量,例如TPL。
通常,来自窗口管理器/ OS的所有窗口消息都将进入单个队列,因此自然而然地将所有UI元素都放在一个线程中。如果尝试从创建线程的线程之外的其他线程直接访问UI元素,则某些框架(例如.Net)实际上会引发异常。
是的。
GUI应用程序应将其使用的线程数量减至最少,原因如下:
- 线程编程非常困难和复杂
- 通常,GUI应用程序一次最多执行两项操作:a)响应用户输入,以及b)响应用户操作或者预期的用户操作执行后台任务(例如数据加载)
因此,通常来说,使用多个线程不会增加应用程序的需求。
当然,该规则也有例外。