我认为多核计算机的要点是它可以同时运行多个线程。在这种情况下,如果您有一台四核计算机,一次运行四个以上线程有什么意义?他们会不会只是在互相浪费时间(CPU资源)?
我认为多核计算机的要点是它可以同时运行多个线程。在这种情况下,如果您有一台四核计算机,一次运行四个以上线程有什么意义?他们会不会只是在互相浪费时间(CPU资源)?
Answers:
答案围绕着线程的目的,即并行性:一次运行多个独立的执行行。在“理想”的系统中,每个内核将有一个线程执行:没有中断。实际上并非如此。即使您有四个核心和四个工作线程,您的进程及其线程也将不断被其他进程和线程切换出。如果您正在运行任何现代OS,则每个进程至少具有一个线程,而许多进程则具有更多线程。所有这些过程都同时运行。您现在可能已经在计算机上运行了数百个线程。如果没有时间从线程中“偷走”线程,您将永远不会遇到这样的情况。(嗯,如果它是实时运行的,,如果您使用的是实时操作系统,或者甚至在Windows上,请使用实时线程优先级。但这很罕见。)
以此为背景,答案是:是的,在一台真正的四核计算机上,如果有四个以上的线程,可能会导致您“相互窃取时间”,但前提是每个线程都需要100%CPU。如果某个线程无法100%正常工作(例如UI线程可能无法正常工作,或者某个线程正在做少量工作或正在等待其他事情),那么安排另一个线程实际上是一个很好的情况。
实际上比这更复杂:
如果您需要同时完成五项工作,该怎么办?一次运行它们,而不是运行四个,然后再运行第五个,则更有意义。
线程真正需要100%CPU的情况很少。例如,当它使用磁盘或网络I / O时,它可能会花费时间等待无所事事。这是非常普遍的情况。
如果您有需要运行的工作,则一种常见的机制是使用线程池。与内核具有相同数量的线程似乎很有意义,但是.Net线程池每个处理器最多可以使用250个线程。我不确定为什么要这样做,但是我的猜测是要与在线程上运行的任务的大小有关。
因此:窃取时间并不是一件坏事(也不是真正的盗窃:这是系统应该工作的方式。)根据线程将要执行的工作类型来编写多线程程序,这可能不是CPU -界。根据性能分析和测量找出所需的线程数。您可能会发现,从任务或作业而不是线程方面进行思考更有用:编写工作对象并将它们分配给要运行的池。最后,除非您的程序确实对性能至关重要,否则不要太担心:)
关键是,尽管当线程数超过核心数时并没有获得真正的加速,但是您可以使用线程来分解不需要相互依赖的逻辑。
即使在一个中等复杂的应用程序中,使用单个线程尝试快速完成所有操作也会使代码的“流”散列。单线程花费大量时间轮询,检查,按需有条件地调用例程,除了一堆细节外,几乎看不到其他东西。
与可以将线程专用于任务的情况形成对比,以便在查看任何单个线程时可以看到该线程在做什么。例如,一个线程可能阻止等待来自套接字的输入,将流解析为消息,过滤消息,并在出现有效消息时将其传递给其他工作线程。工作线程可以处理来自许多其他来源的输入。这些代码中的每一个代码都将显示出清晰,有目的的流程,而无需进行明确的检查以确认没有其他事情要做。
通过这种方式对工作进行分区,使您的应用程序可以依赖操作系统来调度接下来要使用cpu进行的操作,因此您不必在应用程序中的任何地方进行明确的条件检查,以检查哪些内容可能阻塞以及哪些内容可以处理。
如果线程正在等待资源(例如将RAM中的值加载到寄存器,磁盘I / O,网络访问,启动新进程,查询数据库或等待用户输入),则处理器可以在资源,然后返回到第一个线程。这可以减少CPU空闲的时间,因为CPU可以执行数百万次操作而不是闲置。
考虑需要从硬盘驱动器读取数据的线程。在2014年,典型的处理器内核工作在2.5 GHz频率下,每个周期可能执行4条指令。周期时间为0.4 ns,处理器每纳秒可执行10条指令。在典型的机械硬盘驱动器查找时间约为10毫秒的情况下,处理器能够从硬盘驱动器读取值的过程中执行1亿条指令。具有较小缓存(4 MB缓冲区)的硬盘驱动器和具有几GB存储容量的混合驱动器可能会显着提高性能,因为顺序读取或从混合部分读取的数据延迟可能会快几个数量级。
处理器内核可以在线程之间切换(暂停和恢复线程的成本约为100个时钟周期),而第一个线程等待高延迟输入(比寄存器(1个时钟)和RAM(5纳秒)还贵的任何东西)包括磁盘I / O,网络访问(等待时间为250ms),从CD或慢速总线或数据库调用中读取数据。具有比内核更多的线程意味着在解决高延迟任务时可以完成有用的工作。
CPU具有线程调度程序,该线程调度程序为每个线程分配优先级,并允许线程休眠,然后在预定时间后恢复。减少抖动的任务是线程调度程序的工作,如果每个线程在重新进入睡眠状态前仅执行100条指令,就会发生抖动。切换线程的开销将减少处理器内核的总有用吞吐量。
因此,您可能希望将问题分解为合理数量的线程。如果编写代码以执行矩阵乘法,则在输出矩阵中每个单元格创建一个线程可能会过多,而在输出矩阵中每行或每n行创建一个线程可能会减少创建,暂停和恢复线程的开销。
这也是分支预测很重要的原因。如果您有一个if语句需要从RAM加载一个值,但是if和else语句的主体使用已经加载到寄存器中的值,则处理器可以在评估条件之前执行一个或两个分支。条件返回后,处理器将应用相应分支的结果,并丢弃另一个分支的结果。在这里执行可能无用的工作可能比切换到其他线程更好,否则可能会导致崩溃。
随着我们从高时钟速度的单核处理器转移到多核处理器,芯片设计已集中在每个管芯上塞满更多内核,改善内核之间的片上资源共享,更好的分支预测算法,更好的线程切换开销,和更好的线程调度。
上面的大多数答案都涉及性能和同时运行。我将从另一个角度来解决这个问题。
让我们以一个简单的终端仿真程序为例。您必须执行以下操作:
(真正的终端仿真器做得更多,包括潜在地将您键入的内容也回显到显示器上,但是我们暂时将其忽略。)
现在,按照下面的伪代码,从远程读取的循环很简单:
while get-character-from-remote:
print-to-screen character
监视键盘和发送的循环也很简单:
while get-character-from-keyboard:
send-to-remote character
但是,问题在于您必须同时执行此操作。现在,如果没有线程,则代码必须看起来更像这样:
loop:
check-for-remote-character
if remote-character-is-ready:
print-to-screen character
check-for-keyboard-entry
if keyboard-is-ready:
send-to-remote character
即使在这个故意简化的示例中,它也没有考虑到通信的实际复杂性,其逻辑还是很模糊。但是,有了线程,即使在单个内核上,两个伪代码循环也可以独立存在,而无需交织其逻辑。由于这两个线程大部分都是受I / O约束的,因此即使严格地讲,它们比集成循环浪费的CPU资源也更多,但它们不会给CPU带来沉重的负担。
当然,现在实际使用情况比上述情况更为复杂。但是,随着您对应用程序增加更多的关注,集成循环的复杂性将成倍增加。逻辑变得越来越零散,您必须开始使用状态机,协程等技术来使事情易于管理。可管理,但不可读。线程使代码更具可读性。
那为什么不使用线程呢?
好吧,如果您的任务是CPU约束的,而不是I / O约束的,则线程化实际上会降低系统的速度。性能会受到影响。很多情况下。(“丢弃”是一个常见问题,如果您删除了太多的CPU绑定线程。与运行线程内容本身相比,您花费更多的时间更改活动线程)。另外,上述逻辑的原因之一是如此简单,是我特意选择了一个简单(且不现实)的示例。如果您想在屏幕上回显键入的内容,那么在引入共享资源锁定时,您将遭受新的痛苦。仅拥有一个共享资源,这并不是什么大问题,但是随着您有更多资源可共享,它的确开始变得越来越大。
因此,最后,线程涉及很多事情。例如,正如一些人已经说过的那样,这是为了使受I / O约束的进程更具响应性(即使整体效率较低)。这也是使逻辑更易于遵循(但前提是要最小化共享状态)。它涉及很多东西,您必须根据具体情况确定其优点是否大于缺点。
尽管您当然可以根据您的硬件使用线程来加快计算速度,但是出于用户友好的原因,线程的主要用途之一是一次执行多项操作。
例如,如果您必须在后台进行一些处理并且还对UI输入保持响应,则可以使用线程。如果没有线程,则每次尝试进行任何繁重的处理时,用户界面都会挂起。
另请参阅以下相关问题:线程的实际用途
我非常不同意@kyoryu的说法,即理想的数量是每个CPU一个线程。
这样考虑:为什么要有多处理操作系统?在大多数计算机历史记录中,几乎所有计算机都具有一个CPU。但是从1960年代开始,所有“真实”计算机都具有多处理(也称为多任务)操作系统。
您运行多个程序,以便一个程序可以运行,而其他程序则由于IO等原因而被阻止。
让我们搁置有关NT之前的Windows版本是否为多任务处理的争论。从那时起,每个实际的操作系统都具有多任务处理。有些人不会将其公开给用户,但无论如何都不会公开给用户,例如听手机广播,与GPS芯片交谈,接受鼠标输入等。
线程只是效率更高的任务。任务,进程和线程之间没有根本区别。
CPU是一件很浪费的事情,因此,在可能的情况下,有很多事情可以使用。
我会同意,对于大多数程序语言(C,C ++,Java等),编写适当的线程安全代码是很多工作。当今市场上有6个核心CPU,而距离不远的16个核心CPU,我希望人们将不再使用这些旧语言,因为多线程越来越重要。
与@kyoryu的异议只是恕我直言,其余就是事实。
许多线程将处于睡眠状态,等待用户输入,I / O和其他事件。
处理器或CPU是插入系统的物理芯片。一个处理器可以具有多个核心(一个核心是芯片中能够执行指令的部分)。如果一个内核能够同时执行多个线程(一个线程是一条指令的单个序列),则它可以在操作系统中显示为多个虚拟处理器。
进程是应用程序的别称。通常,过程彼此独立。如果一个进程死亡,则不会导致另一个进程也死亡。进程可能进行通信或共享诸如内存或I / O之类的资源。
每个进程都有一个单独的地址空间和堆栈。一个进程可以包含多个线程,每个线程都可以同时执行指令。进程中的所有线程共享相同的地址空间,但是每个线程将拥有自己的堆栈。
希望有了这些定义,并使用这些基础知识进行进一步的研究将有助于您的理解。
实际上,线程的理想用法是每个内核一个。
但是,除非专门使用异步/非阻塞IO,否则很有可能在某个时候阻塞IO线程,这将不使用CPU。
同样,典型的编程语言使得每个CPU使用1个线程有些困难。围绕并发设计的语言(例如Erlang)可以使不使用额外线程变得更加容易。
一些API的设计方式,您别无选择只能在单独的线程中运行它们(任何有阻塞操作的东西)。一个示例是Python的HTTP库(AFAIK)。
通常,这并不是什么大问题(如果有问题,则OS或API应该附带一种可选的异步操作模式,即:)select(2)
,因为这可能意味着线程在等待I /时将处于睡眠状态。 O完成。另一方面,如果某事正在执行大量计算,则您有必须将其放在一个单独的线程中,而不是GUI线程中(除非您喜欢手动多路复用)。
针对您的第一个推测:多核计算机可以同时运行多个进程,而不仅仅是单个进程的多个线程。
回答第一个问题:多个线程的目的通常是在一个应用程序中同时执行多个任务。网络上的经典示例是发送和接收邮件的电子邮件程序以及接收和发送页面请求的Web服务器。(请注意,将Windows之类的系统简化为仅运行一个线程甚至仅运行一个进程,这实际上是不可能的。运行Windows Task Manager,您通常会看到一长串活动进程,其中许多进程将运行多个线程。 )
回答您的第二个问题:大多数进程/线程不是CPU约束的(即,不是连续运行且不间断),而是停止并频繁等待I / O完成。在该等待期间,其他进程/线程可以运行,而无需从等待代码中“窃取”(即使在单核计算机上)。
关键是绝大多数程序员不了解如何设计状态机。能够将所有内容放入自己的线程中,使程序员不必考虑如何有效地表示不同正在进行的计算的状态,以便可以中断它们并在以后恢复它们。
例如,考虑视频压缩,这是一个非常占用CPU的任务。如果您使用的是gui工具,则可能希望界面保持响应状态(显示进度,响应取消请求,调整窗口大小等)。因此,您可以设计编码器软件来一次处理一个大单元(一个或多个帧),然后在独立于UI的单独线程中运行它。
当然,一旦意识到能够保存进行中的编码状态就很好,因此可以关闭程序以重新启动或玩耗时费力的游戏,您就会意识到应该已经学会了如何从开始。要么,要么您决定设计一个操作系统休眠的全新问题,以便您可以挂起和恢复单个应用程序到磁盘...