线程休眠时的线程与内核

时间:2019-07-12 04:58:41

标签: multithreading

我希望确认关于线程和CPU内核的假设。

所有线程都相同。不使用磁盘I / O,线程不共享内存,并且每个线程仅受CPU约束。

  1. 如果我的CPU具有10个内核,并且生成10个线程,则每个线程将具有自己的内核并同时运行。
  2. 如果我使用具有10个内核的CPU启动20个线程,那么这20个线程将在10个内核之间“任务切换”,从而使每个线程大约占每个内核CPU时间的50%。
  3. 如果我有20个线程,但其中10个线程处于睡眠状态,而10个处于活动状态,则10个活动线程将在10个内核上以100%的CPU时间运行。
  4. 处于睡眠状态的线程仅消耗内存,而不消耗CPU时间。当线程仍处于睡眠状态时。例如,全部处于睡眠状态的10,000个线程使用的CPU数量与1个线程处于睡眠状态的CPU数量相同。
  5. 通常,如果您有一系列在并行进程上工作时经常休眠的线程。您可以添加更多线程,然后再添加内核,直到所有内核都100%处于繁忙状态。

我的任何假设都不正确吗?如果可以,为什么?

修改

  • 当我说线程处于睡眠状态时,是指该线程被阻塞特定时间。在C ++中,我将使用sleep_for 为至少指定的sleep_duration阻止当前线程的执行

2 个答案:

答案 0 :(得分:2)

如果我们假设您正在谈论的是在现代OS中使用本机线程支持实现的线程,那么您的陈述或多或少是正确的。

有一些因素可能导致行为偏离“理想”状态。

  1. 如果还有其他用户空间进程,它们可能会与您的应用程序争用资源(CPU,内存等)。这将减少(例如)应用程序可用的CPU。请注意,这将包括诸如负责运行桌面环境的用户空间进程之类的事情。

  2. 操作系统内核将产生各种开销。发生这种情况的地方很多,包括:

    • 管理文件系统。
    • 管理物理/虚拟内存系统。
    • 处理网络流量。
    • 安排进程和线程。

    这将减少您的应用程序可用的CPU。

  3. 线程调度程序通常不会进行完全公平的调度。因此,一个线程可能比另一个线程获得更多的CPU百分比。

  4. 当应用程序具有较大的内存占用并且线程没有良好的内存局部性时,与硬件的交互会很复杂。由于各种原因,占用大量内存的线程会相互竞争,并且会彼此放慢速度。这些交互都被计为“用户进程”时间,但是它们导致线程能够执行较少的实际工作。


所以:

  

1)如果我有10个内核的CPU,并且产生了10个线程,则每个线程将具有自己的内核并同时运行。

由于其他用户进程和操作系统开销,可能并非始终都如此。

  

2)如果我使用具有10个内核的CPU启动20个线程,则这20个线程将在10个内核之间“任务切换”,从而使每个线程大约每个内核的CPU时间的50%。

大约。有开销(见上文)。还有一个问题是,具有相同优先级的不同线程之间的时间分片是相当粗糙的,并且不一定公平。

  

3)如果我有20个线程,但其中10个线程处于睡眠状态,而10个处于活动状态,则10个活动线程将在10个内核上以CPU时间的100%运行。

大约:见上文。

  

4)处于睡眠状态的线程仅消耗内存,而不消耗CPU时间。当线程仍处于睡眠状态时。例如,所有处于睡眠状态的10,000个线程使用与1个线程睡眠相同的CPU数量。

还有一个问题,就是操作系统要消耗CPU来管理睡眠线程。例如让他们入睡,决定何时醒来,重新安排时间。

另一个是线程使用的内存也可能要付出一定的代价。例如,如果用于所有进程(包括所有10,000个线程的堆栈)的内存总和大于可用的物理RAM,则很可能存在分页。而且,这也会占用CPU资源。

  

5)通常,如果您有一系列线程在并行进程上工作时经常睡眠。您可以添加更多线程,然后再添加内核,直到所有内核都100%处于繁忙状态。

不一定。如果虚拟内存使用率超出正常范围(即您正在大量分页),则系统可能不得不在等待从分页设备读取和写入内存页面时空闲一些CPU。简而言之,您需要要考虑内存利用率,否则它将影响CPU利用率。

这也没有考虑线程调度和线程之间的上下文切换。每次操作系统将一个内核从一个线程切换到另一个线程时,它都必须:

  1. 保存旧线程的寄存器。
  2. 刷新处理器的内存缓存
  3. 使VM映射寄存器等无效。这包括@bazza提到的TLB。
  4. 加载新线程的寄存器。
  5. 由于必须进行更多的主内存读取,并且由于先前的缓存无效,导致虚拟机页面转换,因此获得了性能上的提高。

这些开销可能会很大。根据{{​​3}},每个上下文切换通常约为1.2微秒。听起来可能并不多,但是如果您的应用程序正在快速切换线程,则每秒可能会花费很多毫秒。

答案 1 :(得分:1)

正如评论中已经提到的,它取决于许多因素。但是从一般意义上来说,您的假设是正确的。

睡眠

在糟糕的过去,C库可能已将sleep()作为执行毫无意义的工作的循环来实现(例如,将1乘以1,直到经过了所需的时间)。在这种情况下,CPU仍将处于100%繁忙状态。如今,sleep()实际上会导致在必要的时间内调度线程。诸如MS-DOS之类的平台以这种方式工作,但是任何多任务OS都已经有几十年的正确实现。

10,000个睡眠线程将占用更多的CPU时间,因为OS必须在每个时间片滴答(每60ms左右)做出调度判断。准备运行需要检查的线程越多,检查所花费的CPU时间就越多。

转换后备缓冲区

添加比内核更多的线程通常被认为是可以的。但是您可能会遇到翻译后备缓冲区(或其他CPU上的等效缓冲区)的问题。这些是CPU虚拟内存管理方面的一部分,它们本身实际上是内容地址内存。这确实很难实现,所以从来没有那么多。因此,内存分配越多(如果您添加越来越多的线程,分配的内存就越多),则该资源被消耗的越多,以至于OS可能必须开始换入和换出TLB的不同负载才能完成。所有虚拟内存分配都可以访问。如果这开始发生,那么过程中的所有事情都会变得非常非常缓慢。如今,这比20年前的问题要少。

此外,C库中的现代内存分配器(以及因此而构建在顶部的所有其他内容,例如Java,C#等)实际上将非常谨慎地管理对虚拟内存的请求,从而减少了实际需要的时间。操作系统以获取更多虚拟内存。基本上,他们试图从已经拥有的池中提供请求的分配,而不是每个malloc()都导致对OS的调用。这需要TLB的压力。