线程如何节省时间?

我正在学习C#中的线程。 但是,我无法理解线程的哪些方面实际上正在提高性能。

考虑只存在单个核心处理器的情况。 将您的任务拆分为多个线程使用相同的进程上下文(共享资源)并且它们同时运行。 由于线程只是共享时间,为什么它们的运行时间(周转时间)小于单线程进程?

考虑只存在单个核心处理器的情况。 将您的任务拆分为多个线程使用相同的进程上下文(共享资源)并且它们同时运行。 由于线程只是共享时间,为什么它们的运行时间(周转时间)小于单线程进程?

你对任何声称的加速都持怀疑态度是完全正确的。

首先,正如Servy和其他人在他们的答案中指出的那样,如果作业不受处理器限制,那么显然可以有一些加速,因为当处理器空闲等待磁盘或网络回来时,它可能正在做另一个线程的工作

但是假设你有两个处理器绑定的任务,一个处理器,两个线程或一个线程。 在单线程场景中它是这样的:

  • 做100%的工作1.假设这需要1000毫秒。
  • 完成工作的100%工作2.假设这需要1000毫秒。

总时间:两秒钟。 完成的工作总数:两个。 但这是重要的一点: 等待工作1的客户只需一秒即可完成工作。 等待工作2的客户端必须等待两秒钟。

现在,如果我们有两个线程和一个CPU,它会像这样:

  • 完成工作1的10%工作,持续100毫秒。
  • 完成工作2的10%工作,持续100毫秒。
  • 做10%的工作1
  • 完成工作2的10%工作……

同样,总时间为两秒,但这次正在等待工作1的客户端在1.9秒内完成工作,比单线程方案慢近100%!

这就是这里故事的寓意,你完全可以指出。 如果满足以下条件:

  • 这些作业受CPU限制
  • 线程数多于CPU
  • 这项工作仅对其最终结果有用

然后添加更multithreading只会减慢你的速度

诸如任务并行库之类的库是为这种情况设计的; 他们试图弄清楚何时添加更multithreading会使事情变得更糟,并尝试只安排尽可能多的线程来处理服务它们的CPU。

现在,如果不满足任何条件,那么添加更multithreading是个好主意。

  • 如果作业不受CPU限制,那么添加更multithreading允许CPU在空闲时等待网络或磁盘时工作。

  • 如果存在空闲CPU,则添加更multithreading允许调度这些CPU。

  • 如果部分计算结果有用,那么添加更multithreading可以改善这种情况,因为客户端有更多机会使用部分计算结果。 例如,在我们的第二个场景中, 两个作业的客户端每200毫秒获得一次部分结果,这是公平的

在单核CPU中,您获得的优势是通过异步。 使用线程是实现这一目标的一种方式(尽管不是唯一的方法)。

想象一下烹饪一顿饭的过程。 您认为哪个更快:

  1. 开始浇水。 等待它完成。
  2. 加一些面条。 等待他们完成煮熟。
  3. 洗/准备一些蔬菜。
  4. 搅拌炒蔬菜。
  5. 穿上盘子和服务。

或者改为:

  1. 开始浇水。
  2. 虽然水沸腾洗/准备一些蔬菜。
  3. 在煮沸的水中加入一些面条。
  4. 在煮熟面条的同时搅拌蔬菜。
  5. 穿上盘子和服务。

根据我的经验,第二个更快。

这里的一般想法是,在编程的许多情况下,您将需要一些时间的操作,但它不需要完成CPU的工作。 一个常见的例子是IO。 当您向数据库发送请求以获取某些信息时,在等待该请求返回时,您可以执行其他操作。 也许你可以发送几个请求,然后等待它们完成,而不是开始一个,等待它,然后开始下一个,等待,等等(尽管有时你必须做后者)。

现在,如果您需要做的工作是CPU绑定工作,那么如果您的CPU上有多个内核,那么您实际上只会从线程中获益,这样工作实际上可以并行执行,而不仅仅是异步执行。 例如,许多与图形相关的工作(乘以矩阵,给出一个简单的例子)通常涉及做很多基本的数学运算。 如果您有多个核心,这些操作通常可以很好地扩展。 如果您没有多个核心(或GPU,实际上是一个具有许多非常小且简单的核心的CPU),那么使用线程没有多大意义。

你的大多数评论是正确的,但我也会把我的两分钱扔出去(并列出这里的评论):

Jonesy:“线程在多核环境中最有效” – >是的,但这是一个单核cpu …所以我会回到这个。

KooKiz和John Sibly:他们都提到了I / O. 您的机器在100%的时间内不会以全功率运转。 还有很多其他事情需要花费时间,在这些事件中,你的CPU会rest一下。

(参考:I / O可以是网络传输,硬盘/ RAM读取,SQL查询等。任何将新数据带入CPU或从CPU卸载数据的东西)

这些rest是你的cpu可以做其他事情的时间。 如果你有一个单核心cpu(我们暂时忽略超线程)和一个单线程应用程序,那么它可以运行得很开心。 但是,它不会持续运行。 CPU调度会给它一个或两个循环,然后转移到其他东西,然后过一会儿再回到你的程序,给它几个循环,继续前进,等等。这使得ILLUSION能够做到“多个在单核心CPU上的事情。

现在,因为这是一个普通的程序而不是一些疯狂的小程序集程序,你直接在缓存中写入值,程序会将数据存储在RAM中……与CPU缓存相比,存储介质相对较慢。 因此,加载值需要时间。

在此期间,您的CPU可能没有更好的事情要做。 在这里您可以看到multithreading应用程序的加速,即使在单核上也是如此。 另一个线程将填充那些额外的CPU周期,否则CPU将处于空闲状态。

请注意,您不太可能看到2:1的加速。 如果是这样的话,你的2线程程序更有可能只能看到10-20%的速度提升。 请记住,“其他”线程(在任何给定点上是不执行I / O的线程)只会在第一个线程执行I / O时真正以其满容量运行。

但是,您经常可以看到WORSE时间。 这是因为你的CPU现在必须花费更多的时间在你的进程中的线程之间切换(记住,我们一次只能运行一件事!)。 这称为开销 。 第二个线程产生的开销超过了它可以弥补的开销,因此整个过程总体上变慢了。

在多核计算机上,您有两个物理执行程序…这意味着第二个线程可以使用一个完整的新核心。 这意味着它不必与执行时间中的许多其他事物竞争。 因此,我们在这里得到了大幅度的加速。

当然,你有多个程序在群集上执行,但我们会再保存一次。

如果将计算分为并发控制线程,则会更改周转时间。

示例1:线程使其变得更糟

假设我们想要进行两次计算,每次计算需要10分钟。

如果我们连续安排这些(没有multithreading),那么在10分钟内我们将得到一次计算的结果,而在另外10分钟内,我们将得到另一次计算的结果。

如果我们在计算之间进行时间分割,那么我们将不得不等待20分钟,在通过之后,我们突然得到两个结果。

示例2:线程使其更好

假设我们想进行两次计算。 一分钟需要一分钟,另一分钟需要59分钟, 但我们不知道这一点 。 (请记住,我们只是一个不了解代码的调度程序。)

如果我们一个接一个地运行这两个工作,可能会先安排59分钟的工作。 那么我们必须等待59分钟获得一个结果,然后再等一分钟获得第二个结果。 基本上一小时等待两个结果。

如果我们幸运的话,我们最终会先运行较短的工作,并在1分钟内获得第一个结果,在59分钟后获得第二个结果:平均周转时间要好得多。

但是假设我们在具有线程的作业之间切换时间。 然后我们在2分钟内获得第一份工作的结果,在58分钟后获得第二份工作的结果。 这几乎与第二种情况一样好,但无需预测哪个作业将是短作业。

对于纯粹的CPU绑定任务进行时间切片的线程有助于避免病态情况,其中一个非常大的工作会延迟完成大型工作所需的全部时间。

重要的是要注意,线程本身不会使进程更快 – 有时,竞争相同的进程将增加必要的运行时间而不是减少它。 一个好的评估是,您所期望的场景是否会从multithreading中受益。

线程的基本要点是利用可用资源进行多任务处理 – 正如KooKiz所说,就像在可用时使用剩余的CPU时间一样。 但是你是对的,可能存在使用线程不会改善运行时的情况。

但是,即使对于单核系统,也存在multithreading可以提高性能的情况。 当一个进程正在等待某个进程时,它不会锁定串联运行的任何其他进程。 根据等待发生的时间长短,您的单核可以在其他独立进程之间跳转,从而节省时间。

完全正确的做法是在单核CPU上使用多个线程不会改善总CPU时间 。 实际上,由于上下文切换的代价 ,它可能会使情况变得更糟。

但总的CPU时间只是故事的一半……

流畅的UI

线程也是实现异步的一种方式,这对流体用户界面尤为重要。 例如,如果您执行昂贵的CPU绑定处理并在同一个线程上处理UI,则程序将(从用户的角度)显示为暂时“挂起”,直到处理完成。 但是,如果将处理推送到后台线程,UI线程可以继续响应用户的输入和/或继续通知用户进度。

非CPU绑定处理

最重要的是,并非所有处理都受CPU限制。 如果您执行诸如读取文件,访问数据库或调用Web服务之类的操作,则在等待外部资源时将阻止该线程(并且CPU未得到充分利用)。 如果还有其他线程需要做一些工作,他们可以在第一个线程被阻塞时使用CPU周期。

TPL

在C#的情况下,您可能希望使用任务并行库进行并发(以及async-await for asynchrony),而不是尝试自己管理低级线程。 默认情况下,将在线程池上调度任务 s,避免线程过多(以及上下文切换)的危险。

有关详细信息,请参阅Microsoft .NET并行编程 。