对于多线程编程的最常见目的,Microsoft的异步方法是很好的替代品:提高对IO任务的响应能力。
但是,重要的是要意识到异步方法根本无法提高性能,也无法提高对CPU密集型任务的响应能力。
多线程响应
用于响应性的多线程是使程序在繁重的IO任务或繁重的计算任务期间保持响应能力的传统方法。您可以将文件保存在后台线程中,以便用户可以继续工作,而不必等待硬盘驱动器完成其任务。IO线程通常会阻塞等待写入的某些部分完成,因此上下文切换非常频繁。
同样,执行复杂的计算时,您希望允许定期进行上下文切换,以便UI可以保持响应状态,并且用户不会认为程序已崩溃。
通常,这里的目标不是让多个线程在不同的CPU上运行。相反,我们只是想让上下文切换在长时间运行的后台任务和UI之间发生,以便UI能够在后台任务运行时更新并响应用户。通常,UI不会占用太多CPU功能,并且线程框架或操作系统通常会决定在同一CPU上运行它们。
实际上,由于上下文切换的额外成本,我们实际上失去了整体性能,但是我们不在乎,因为CPU的性能并不是我们的目标。我们知道通常我们的CPU能力超出了需要,因此我们在多线程方面的目标是在不浪费用户时间的情况下为用户完成任务。
“异步”替代
“异步方法”通过在单个线程中启用上下文切换来更改此情况。这样可以保证我们所有的任务都可以在单个CPU上运行,并且可以在较少的线程创建/清理和较少的线程之间实际上下文切换方面提供适度的性能改进。
代替创建新线程来等待网络资源的接收(例如,下载图像),使用一种async
方法,该方法await
使图像可用,并且同时产生调用方法。
这里的主要优点是您不必担心避免死锁之类的线程问题,因为您根本不使用锁和同步,并且程序员设置后台线程并返回的工作量少了一些当结果返回时,在UI线程上单击,以便安全地更新UI。
我还没有深入研究技术细节,但是我的印象是,偶尔进行少量CPU活动来管理下载不是一项针对单独线程的任务,而是一项类似于UI事件队列中的任务,以及下载完成后,将从该事件队列中恢复异步方法。换句话说,await
意味着类似于“检查所需的结果是否可用,如果没有,将我放回到该线程的任务队列中”。
请注意,这种方法无法解决CPU密集型任务的问题:没有等待数据,因此,如果不创建实际的后台工作线程,就无法获得需要进行的上下文切换。当然,在普遍使用异步方法的程序中,使用异步方法启动后台线程并返回结果可能仍然很方便。
多线程性能
自从您谈论“性能”以来,我还想讨论如何将多线程用于提高性能,这对于单线程异步方法是完全不可能的。
当您实际上处于一个CPU上没有足够的CPU能力并且想要使用多线程来提高性能的情况下,实际上通常很难做到。另一方面,如果一个CPU的处理能力不足,它通常也是使程序能够在合理的时间范围内完成您想完成的工作的唯一解决方案,这使工作值得。
琐碎的平行主义
当然,有时候从多线程获得真正的加速可能很容易。
如果您碰巧有大量独立的计算密集型任务(即,对于确定结果必须执行的计算而言,其输入和输出数据很小的任务),则通常可以通过以下方式显着提高速度:创建一个线程池(根据可用CPU的数量适当确定大小),并让主线程分发工作并收集结果。
实用的多线程性能
我不想让自己成为专家,但我的印象是,通常来说,最近出现的最实用的多线程性能正在寻找应用程序中琐碎的并行性并使用多个线程收获好处。
与任何优化一样,通常最好在分析程序的性能并确定热点后进行优化:通过任意决定该部分应在一个线程中运行而该部分在另一个线程中运行,很容易减慢程序速度,首先确定这两个部分是否都占用了大量CPU时间。
额外的线程意味着更多的设置/拆卸成本,或者更多的上下文切换或更多的CPU间通信成本。如果在单独的CPU上没有做足够的工作来弥补这些成本,并且由于响应性原因而不必成为单独的线程,那么它将使速度变慢而没有任何好处。
寻找没有相互依赖性的任务,这些任务占用程序运行时的很大一部分。
如果它们之间没有相互依赖性,那么这就是微不足道的并行性,您可以轻松地为每个线程设置线程并享受其中的好处。
如果可以找到相互依存性有限的任务,从而使锁定和同步来交换信息不会显着降低它们的速度,那么多线程可以加快速度,前提是您要小心避免由于同步或同步时逻辑错误而导致死锁的危险。由于在必要时未同步,导致错误结果。
或者,某些更常见的多线程应用程序不是(某种意义上)不是在寻求预定算法的加速,而是为了他们计划编写的算法有更大的预算:如果您正在编写游戏引擎,并且您的AI必须在您的帧频范围内做出决定,如果您可以给自己的CPU分配预算,则通常可以给它更大的CPU周期预算。
但是,请确保对线程进行概要分析,并确保它们做了足够的工作以弥补某些时候的成本。
并行算法
使用多个处理器可以解决很多问题,但是这些问题过于单一,无法简单地在多个CPU之间分配。
相对于最佳可用非并行算法,并行算法的big-O运行时必须仔细分析,因为CPU间的通信成本很容易消除使用多个CPU带来的任何好处。通常,与在每个CPU上使用计算相比,它们使用的CPU间通信(以big-O表示)必须更少。
目前,它仍然是学术研究的主要空间,部分原因是需要进行复杂的分析,部分原因是琐碎的并行性非常普遍,部分原因是我们的计算机上还没有那么多的CPU内核,因此会出现问题。无法在一个合理的时间范围内解决一个CPU可能在一个合理的时间范围内使用我们所有的CPU解决。