我正在学习C#中的线程。但是,我无法理解线程的哪些方面实际上正在提高性能。
考虑只存在单个核心处理器的场景。将任务拆分为多个线程使用相同的进程上下文(共享资源),并且它们同时运行。由于线程只是共享时间,为什么它们的运行时间(周转时间)小于单线程进程?
答案 0 :(得分:28)
在单核CPU中,您获得的优势是通过异步。使用线程是实现这一目标的一种方式(虽然不是唯一的方法)。
想象一下烹饪一顿饭的过程。您认为哪个更快:
或者改为:
根据我的经验,第二个更快。
这里的一般想法是,在编程的许多情况下,您将需要一些操作需要一些时间,但它不需要完成CPU的工作。一个常见的例子是IO。当您向数据库发送请求以获取某些信息时,在您等待该请求返回时,您可以执行其他操作。也许你可以发送几个请求,然后等待它们完成,而不是开始一个,等待它,然后开始下一个,等待,等等(尽管有时你必须做后者)。
现在,如果您需要做的工作是CPU绑定工作,那么如果您的CPU上有多个内核,那么您实际上只会从线程中获益,这样工作实际上可以并行完成,而不仅仅是异步工作。例如,许多与图形相关的工作(乘以矩阵,给出一个简单的例子)通常涉及进行基本数学的 lot 。如果您有多个核心,这些操作通常可以很好地扩展。如果您没有多个核心(或GPU,实际上是具有非常小且简单核心的批次的CPU),那么使用线程没有多大意义。
答案 1 :(得分:25)
考虑只存在单个核心处理器的场景。将任务拆分为多个线程使用相同的进程上下文(共享资源),并且它们同时运行。由于线程只是共享时间,为什么它们的运行时间(周转时间)小于单线程进程?
对于任何声称的加速都持怀疑态度是完全正确的。
首先,正如Servy和其他人在他们的答案中指出的那样,如果作业不是处理器绑定那么显然可以有一些加速,因为当处理器空闲等待磁盘或网络回来,它可能正在做另一个线程的工作。
但是假设你有两个处理器绑定的任务,一个处理器,两个线程或一个线程。在单线程场景中它是这样的:
总时间:两秒钟。完成的工作总数:两个。但这里有重要的一点:等待工作的客户1只用了一秒就完成了工作。等待工作2的客户不得不等待两秒钟。
现在,如果我们有两个线程和一个CPU,它就像这样:
同样,总时间为2秒,但这次正在等待工作1的客户端在1.9秒内完成工作,比单线程方案慢近100%!
这就是这里故事的寓意,指出你是完全正确的。如果满足以下条件:
然后添加更多线程只会减慢你的速度。
任务并行库等库是针对此场景设计的;他们试图找出何时添加更多线程会使事情变得更糟,并尝试只调度与为其提供服务的CPU一样多的线程。
现在,如果其中任何条件不,则添加更多线程是个好主意。
如果作业不受CPU限制,那么添加更多线程可以使CPU在空闲时等待网络或磁盘工作。
如果有空闲的CPU,则添加更多线程可以安排这些CPU。
如果部分计算结果有用,那么添加更多线程可以改善这种情况,因为客户端有更多机会使用部分计算结果。例如,在我们的第二个场景中,两个作业的客户端每200毫秒获得部分结果,这是 fair 。
答案 2 :(得分:3)
你的大多数评论是正确的,但我也会把我的两分钱扔掉(并在这里列出评论):
Jonesy:“线程在多核环境中效率最高” - >是的,但这是一个单核CPU ...所以我会回到这个。
KooKiz和John Sibly:他们都提到了I / O.您的机器在100%的时间内不会以全功率运转。还有很多其他事情需要花费时间,在这些事件中,你的CPU会休息一下。
(参考点:I / O可以是网络传输,硬盘/ RAM读取,SQL查询等。任何可以为CPU带来新数据或从CPU卸载数据的东西)
这些休息是你的cpu可以做其他事情的时间。如果你有一个单核心cpu(我们暂时忽略超线程)和一个单线程应用程序,那么它可以运行得很开心。但是,它不会持续运行。 CPU调度会给它一个或两个循环,然后转到其他东西,然后过一会儿再回到你的程序,给它几个循环,继续等等。这使ILLUSION能够做到“多个事情“在一个核心CPU上。”
现在,由于这是一个普通的程序而不是一些疯狂的小程序集程序,你直接在缓存中写入值,程序会将数据存储在RAM中......与CPU缓存相比,存储介质相对较慢。因此,加载值需要时间。
在此期间,您的CPU可能没有更好的事情可做。在这里您可以看到多线程应用程序的加速,即使在单核上也是如此。另一个线程将填充那些额外的CPU周期,否则CPU将处于空闲状态。
请注意,你不太可能看到2:1的加速。如果是这样的话,你的2线程程序更有可能只能看到10-20%的速度提升。请记住,“其他”线程(在任何给定点上是不执行I / O的线程)只会在第一个线程执行I / O时才真正以其满容量运行。
但是,您经常可以看到WORSE时间。这是因为你的CPU现在必须花费更多的时间在你的进程中的线程之间切换(记住,我们一次只能运行一件事!)。这称为开销。第二个线程产生的开销比它可以弥补的更多,因此整个过程会减慢。
在多核计算机上,你有两个物理执行器......这意味着第二个线程可以使用一个完整的新核心。这意味着它不必与执行时间中的许多其他事物竞争。因此,我们在这里得到了大幅度的加速。
当然,你有多个程序在群集上执行,但我们会将其保存一段时间。
答案 3 :(得分:2)
如果将计算分为并发控制线程,则会改变周转时间。
假设我们想要进行两次计算,每次计算需要10分钟。
如果我们连续安排这些(没有多线程),那么在10分钟内我们将得到一次计算的结果,而在另外10分钟内,我们将得到另一次计算的结果。
如果我们在计算之间进行时间分割,那么我们将不得不等待20分钟,在此过程中,我们突然得到两个结果。
假设我们想进行两次计算。一个会花一分钟,另一个59分钟,但我们不知道这个。 (请记住,我们只是一个不了解代码的调度程序。)
如果我们一个接一个地运行这两个工作,可能会先安排59分钟的工作。那么我们必须等待59分钟获得一个结果,然后再等一分钟获得第二个结果。基本上一个小时等待两个结果。
如果我们幸运的话,我们最终会先运行较短的工作,并在1分钟内获得第一个结果,在59分钟后获得第二个结果:平均周转时间要好得多。
但是假设我们在具有线程的作业之间切换时间。然后我们在2分钟内获得第一份工作的结果,在58分钟后获得第二份工作的结果。这几乎与第二种情况一样好,但无需预测哪一项工作将是短期工作。
对纯粹的CPU绑定任务进行时间切片的线程有助于避免病态情况,其中一个非常大的工作会延迟完成大型工作所需的全部时间。
答案 4 :(得分:0)
重要的是要注意,线程不会固有地使进程更快 - 有时,竞争相同的进程将增加必要的运行时间而不是减少它。一个很好的评估是,你想要的场景是否会从多线程中受益。
线程的基本要点是利用可用资源进行多任务 - 正如KooKiz所说,就像在可用时使用剩余的CPU时间一样。但是你是对的,可能会出现使用线程不会改善运行时的情况。
但是,即使对于单核系统,也存在多线程可以提高性能的情况。当一个进程正在等待某个进程时,它不会锁定串联运行的任何其他进程。根据等待的时间长短,您的单核可以在其他独立进程之间跳转,从而节省时间。
答案 5 :(得分:0)
在单核CPU上使用多个线程不会改善总CPU时间,这是完全正确的。事实上,由于price of context switching,它可能会使情况变得更糟。
但总CPU时间只是故事的一半......
线程也是实现异步的一种方式,这对流体用户界面尤为重要。例如,如果您执行昂贵的CPU绑定处理并在同一线程上处理UI,则程序将(从用户的角度)显示为暂时“挂起”,直到处理完成。但是如果你将处理推送到后台线程,UI线程可以继续响应用户的输入和/或继续通知用户进度。
最重要的是,并非所有处理都受CPU限制。如果您执行诸如读取文件,访问数据库或调用Web服务之类的操作,则在等待外部资源时将阻止该线程(并且CPU未得到充分利用)。如果还有其他线程需要做一些工作,他们可以在第一个线程被阻塞时使用CPU周期。
对于C#,您可能希望使用Task Parallel Library进行并发(而async-await进行异步),而不是尝试自己管理低级线程。默认情况下,将在线程池上调度Task,以避免线程过多(以及上下文切换)的危险。