多线程:多于核心的线程有什么意义?


142

我认为多核计算机的要点是它可以同时运行多个线程。在这种情况下,如果您有一台四核计算机,一次运行四个以上线程有什么意义?他们会不会只是在互相浪费时间(CPU资源)?


52
我们享受这些类型的问题,他们质疑很基本的东西,这是采取granted..keep未来..
SRINIVAS雷迪Thatiparthy

6
您上次在四核计算机上同时运行Firefox,MS Word,Winamp,Eclipse和下载管理器(四个以上的程序/进程)是什么时候?此外,单个应用程序有时可能会产生四个以上的线程-那又如何呢?
Amarghosh

1
偷窃不一定是坏事。对于需要耗费时间的重要任务,您可能具有更高优先级的线程。
kichik '16

1
@Amarghosh我想这就是问题所在,如果单个应用程序似乎并没有带来任何性能优势,为什么它可能希望产生比内核更多的线程。您的带有四个以上程序的示例在这里并不十分相关。正如您正确指出的那样,这些就是过程。操作系统多任务功能(进程多路复用)与一个进程中的线程关系不大。
Aleksandr Ivannikov

Answers:


81

答案围绕着线程的目的,即并行性:一次运行多个独立的执行行。在“理想”的系统中,每个内核将有一个线程执行:没有中断。实际上并非如此。即使您有四个核心和四个工作线程,您的进程及其线程也将不断被其他进程和线程切换出。如果您正在运行任何现代OS,则每个进程至少具有一个线程,而许多进程则具有更多线程。所有这些过程都同时运行。您现在可能已经在计算机上运行了数百个线程。如果没有时间从线程中“偷走”线程,您将永远不会遇到这样的情况。(嗯,如果它是实时运行的,,如果您使用的是实时操作系统,或者甚至在Windows上,请使用实时线程优先级。但这很罕见。)

以此为背景,答案是:是的,在一台真正的四核计算机上,如果有四个以上的线程,可能会导致您“相互窃取时间”,但前提是每个线程都需要100%CPU。如果某个线程无法100%正常工作(例如UI线程可能无法正常工作,或者某个线程正在做少量工作或正在等待其他事情),那么安排另一个线程实际上是一个很好的情况。

实际上比这更复杂:

  • 如果您需要同时完成五项工作,该怎么办?一次运行它们,而不是运行四个,然后再运行第五个,则更有意义。

  • 线程真正需要100%CPU的情况很少。例如,当它使用磁盘或网络I / O时,它可能会花费时间等待无所事事。这是非常普遍的情况。

  • 如果您有需要运行的工作,则一种常见的机制是使用线程池。与内核具有相同数量的线程似乎很有意义,但是.Net线程池每个处理器最多可以使用250个线程。我不确定为什么要这样做,但是我的猜测是要与在线程上运行的任务的大小有关。

因此:窃取时间并不是一件坏事(也不是真正的盗窃:这是系统应该工作的方式。)根据线程将要执行的工作类型来编写多线程程序,这可能不是CPU -界。根据性能分析和测量找出所需的线程数。您可能会发现,从任务或作业而不是线程方面进行思考更有用:编写工作对象并将它们分配给要运行的池。最后,除非您的程序确实对性能至关重要,否则不要太担心:)


16
+1表示“但仅当每个单独的线程需要100%CPU时”。那是我没有意识到自己所做的假设。
Nick Heiner 2010年

一个好问题的绝妙答案。谢谢!
Edgecase

53

仅仅因为一个线程存在并不总是意味着它正在积极运行。线程的许多应用程序涉及一些线程进入睡眠状态,直到它们需要做某事为止-例如,用户输入触发线程唤醒,执行一些处理然后返回睡眠状态。

本质上,线程是可以彼此独立运行的单独任务,无需了解另一任务的进度。与您同时运行的能力相比,可能有更多的此类需求。即使它们有时不得不排成一排,他们仍然对方便有用。


11
说得好。“每个CPU一个线程”参数仅适用于CPU绑定的代码。异步编程是使用线程的另一个原因。
约书亚·戴维斯

26

关键是,尽管当线程数超过核心数时并没有获得真正的加速,但是您可以使用线程来分解不需要相互依赖的逻辑。

即使在一个中等复杂的应用程序中,使用单个线程尝试快速完成所有操作也会使代码的“流”散列。单线程花费大量时间轮询,检查,按需有条件地调用例程,除了一堆细节外,几乎看不到其他东西。

与可以将线程专用于任务的情况形成对比,以便在查看任何单个线程时可以看到该线程在做什么。例如,一个线程可能阻止等待来自套接字的输入,将流解析为消息,过滤消息,并在出现有效消息时将其传递给其他工作线程。工作线程可以处理来自许多其他来源的输入。这些代码中的每一个代码都将显示出清晰,有目的的流程,而无需进行明确的检查以确认没有其他事情要做。

通过这种方式对工作进行分区,使您的应用程序可以依赖操作系统来调度接下来要使用cpu进行的操作,因此您不必在应用程序中的任何地方进行明确的条件检查,以检查哪些内容可能阻塞以及哪些内容可以处理。


1
这是一个有趣的想法...我一直听说多线程应用程序是复杂性的净增加,但是您所说的是有道理的。
Nick Heiner 2010年

如果未充分分离其关注点,则对应用程序进行多线程处理会增加复杂性。如果设计时考虑的问题(和共享状态)之间的重叠最小,那么在复杂性问题上可以节省成本。
我的正确观点2010年

有多种方法可以构造单线程应用程序,以便在编写程序时更清楚地控制流程。OTOH,如果您可以构造线程,使它们仅彼此传递消息(而不是共享资源),那么弄清正在发生的事情并使所有工作正常很简单。
Donal Fellows 2010年

1
应该指出的是,使用线程只能简化某些事情。通常,人们试图使两个线程完成应该由一个线程正确完成的工作,而复杂性又成倍增加。其症状是过度沟通和同步,以协调某些期望的结果。
JustJeff 2010年

15

如果线程正在等待资源(例如将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语句的主体使用已经加载到寄存器中的值,则处理器可以在评估条件之前执行一个或两个分支。条件返回后,处理器将应用相应分支的结果,并丢弃另一个分支的结果。在这里执行可能无用的工作可能比切换到其他线程更好,否则可能会导致崩溃。

随着我们从高时钟速度的单核处理器转移到多核处理器,芯片设计已集中在每个管芯上塞满更多内核,改善内核之间的片上资源共享,更好的分支预测算法,更好的线程切换开销,和更好的线程调度。


尽管可以使用单个线程和一个队列来完成相同的工作:\\在2-4个内核上拥有80个线程真的有什么好处,而不是仅拥有2-4个内核就可以在任务到达时立即吞噬任务和他们无关吗?
德米特里(Dmitry)

8

上面的大多数答案都涉及性能和同时运行。我将从另一个角度来解决这个问题。

让我们以一个简单的终端仿真程序为例。您必须执行以下操作:

  • 监视来自远程系统的传入字符并显示它们
  • 监视来自键盘的内容并将其发送到远程系统

(真正的终端仿真器做得更多,包括潜在地将您键入的内容也回显到显示器上,但是我们暂时将其忽略。)

现在,按照下面的伪代码,从远程读取的循环很简单:

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约束的进程更具响应性(即使整体效率较低)。这也是使逻辑更易于遵循(但前提是要最小化共享状态)。它涉及很多东西,您必须根据具体情况确定其优点是否大于缺点。


6

尽管您当然可以根据您的硬件使用线程来加快计算速度,但是出于用户友好的原因,线程的主要用途之一是一次执行多项操作。

例如,如果您必须在后台进行一些处理并且还对UI输入保持响应,则可以使用线程。如果没有线程,则每次尝试进行任何繁重的处理时,用户界面都会挂起。

另请参阅以下相关问题:线程的实际用途


UI处理是IO绑定任务的经典示例。只有一个CPU内核同时执行处理和IO任务是不好的。
多纳研究员2010年

6

我非常不同意@kyoryu的说法,即理想的数量是每个CPU一个线程。

这样考虑:为什么要有多处理操作系统?在大多数计算机历史记录中,几乎所有计算机都具有一个CPU。但是从1960年代开始,所有“真实”计算机都具有多处理(也称为多任务)操作系统。

您运行多个程序,以便一个程序可以运行,而其他程序则由于IO等原因而被阻止。

让我们搁置有关NT之前的Windows版本是否为多任务处理的争论。从那时起,每个实际的操作系统都具有多任务处理。有些人不会将其公开给用户,但无论如何都不会公开给用户,例如听手机广播,与GPS芯片交谈,接受鼠标输入等。

线程只是效率更高的任务。任务,进程和线程之间没有根本区别。

CPU是一件很浪费的事情,因此,在可能的情况下,有很多事情可以使用。

我会同意,对于大多数程序语言(C,C ++,Java等),编写适当的线程安全代码是很多工作。当今市场上有6个核心CPU,而距离不远的16个核心CPU,我希望人们将不再使用这些旧语言,因为多线程越来越重要。

与@kyoryu的异议只是恕我直言,其余就是事实。


5
如果您有很多处理器绑定线程,那么理想的数量是每个CPU一个(或者可能更少),以便剩下的一个用于管理所有I / O和OS以及所有其他东西。如果您有IO绑定线程,则可以在单个CPU上堆叠很多。不同的应用程序具有不同的处理器绑定和IO绑定任务。那是完全自然的,但是为什么要对通用声明要小心。
Donal Fellows 2010年

1
当然,线程和进程之间最重要的区别是Windows上没有fork(),因此进程创建的确非常昂贵,导致线程的过度使用。
ninjalj 2010年

除了蛋白质折叠,SETI等外,没有任何实际的用户任务需要很长时间进行计算。总是需要从用户那里获取信息,与磁盘对话,与DBMS对话,等等。是的,fork()的开销是Cutler与DEC的其他人一起诅咒NT的众多事情之一。
fishtoprecords 2010年

5

想象一下一个Web服务器必须服务任意数量的请求。您必须并行处理请求,因为否则每个新请求都必须等待所有其他请求都完成(包括通过Internet发送响应)。在这种情况下,大多数Web服务器的核心数量要少于它们通常服务的请求数量。

这也使服务器开发人员更容易:您只需要编写一个处理请求的线程程序,就不必考虑存储多个请求,为它们服务的顺序等等。


2
您正在为一个支持线程但没有多路复用能力的操作系统编写软件。我认为Web服务器可能是一个不好的例子,因为在这种情况下,多路复用io几乎总是比产生比内核更多的线程更有效率。
杰森·可可

3

许多线程将处于睡眠状态,等待用户输入,I / O和其他事件。


当然。只需在Windows上使用任务管理器或在实际OS上使用TOP,即可查看有多少任务/进程。总是90%或更高。
fishtoprecords 2010年

2

线程可以帮助提高UI应用程序的响应速度。此外,您可以使用线程从内核中获取更多工作。例如,在一个内核上,您可以让一个线程进行IO,而另一个线程进行一些计算。如果它是单线程的,则内核实际上可能处于空闲状态,以等待IO完成。这是一个相当高级的示例,但是绝对可以使用线程来使您的cpu更加困难。


更具体地说,一个线程可以等待 I / O,而另一个线程可以进行计算。如果I / O占用(大量)CPU周期,则在单独的线程中运行它不会有任何好处。这样做的好处是,您的计算线程可以在I / O线程不停地旋转,等待大型铝制圆柱体旋转到位,或者数据包从冰岛通过电线到达时等运行。

2

处理器或CPU是插入系统的物理芯片。一个处理器可以具有多个核心(一个核心是芯片中能够执行指令的部分)。如果一个内核能够同时执行多个线程(一个线程是一条指令的单个序列),则它可以在操作系统中显示为多个虚拟处理器。

进程是应用程序的别称。通常,过程彼此独立。如果一个进程死亡,则不会导致另一个进程也死亡。进程可能进行通信或共享诸如内存或I / O之类的资源。

每个进程都有一个单独的地址空间和堆栈。一个进程可以包含多个线程,每个线程都可以同时执行指令。进程中的所有线程共享相同的地址空间,但是每个线程将拥有自己的堆栈。

希望有了这些定义,并使用这些基础知识进行进一步的研究将有助于您的理解。


2
我完全看不出这是如何解决他的问题的。我对他的问题的解释是关于内核的线程使用和可用资源的最佳使用,或者关于线程数量增加时线程的行为,或者总之。
大卫2010年

@David可能不是我的问题的直接答案,但我仍然觉得我是通过阅读中学到的。
Nick Heiner 2010年

1

实际上,线程的理想用法是每个内核一个。

但是,除非专门使用异步/非阻塞IO,否则很有可能在某个时候阻塞IO线程,这将不使用CPU。

同样,典型的编程语言使得每个CPU使用1个线程有些困难。围绕并发设计的语言(例如Erlang)可以使不使用额外线程变得更加容易。


使用线程执行定期任务是一个非常普遍且受欢迎的工作流,如果它们窃取了一个内核,那将远不理想。
尼克·巴斯汀

@Nick Bastin:是的,但是将这些任务放入任务队列并从该队列执行(或类似策略)会更加有效。为了获得最佳效率,每个内核1个线程胜过所有线程,因为它避免了不必要的上下文切换和分配额外堆栈的开销。无论如何,定期任务必须在“活动”时窃取一个内核,因为cpu实际上只能在每个内核上执行一个任务(如果可用,还要加上诸如超线程之类的东西)。
kyoryu 2010年

@Nick Bastin:不幸的是,正如我在主要答复中所说的那样,大多数现代语言都无法很好地实现易于有效实现这一目标的系统,因此您不得不付出很多努力来对抗这种语言的典型用法。
kyoryu 2010年

我的意思不是说每个内核有一个线程不是最优的,而是每个内核有一个线程是梦dream以求的(除非您是嵌入式的),而尝试实现它是浪费时间,所以您也可能尽一切可能使您轻松(并且无论如何在现代调度程序中都不会降低效率),而不是尝试优化正在使用的线程数。我们是否应该无缘无故地增加线程数量?当然不是,但是不管线程如何,您是否在不必要地浪费计算机资源都是一个问题。
Nick Bastin 2010年

@Nick Bastin:因此,总而言之,每个内核一个线程是理想的,但实际上实现该线程的可能性很小。在谈论实际实现这一目标的可能性时,我可能应该比“有点困难”强一些。
kyoryu 2010年

1

一些API的设计方式,您别无选择只能在单独的线程中运行它们(任何有阻塞操作的东西)。一个示例是Python的HTTP库(AFAIK)。

通常,这并不是什么大问题(如果有问题,则OS或API应该附带一种可选的异步操作模式,即:)select(2),因为这可能意味着线程在等待I /时将处于睡眠状态。 O完成。另一方面,如果某事正在执行大量计算,则您必须将其放在一个单独的线程中,而不是GUI线程中(除非您喜欢手动多路复用)。


1

我知道这是一个非常老的问题,有很多好的答案,但是我在这里指出在当前环境中很重要的一些事情:

如果要设计用于多线程的应用程序,则不应针对特定的硬件设置进行设计。多年来,CPU技术一直在飞速发展,内核数也在稳步增长。如果您故意将应用程序设计为仅使用4个线程,则可能会限制自己使用八核系统(例如)。现在,即使是20核系统也可以在市场上买到,所以这样的设计肯定弊大于利。


0

针对您的第一个推测:多核计算机可以同时运行多个进程,而不仅仅是单个进程的多个线程。

回答第一个问题:多个线程的目的通常是在一个应用程序中同时执行多个任务。网络上的经典示例是发送和接收邮件的电子邮件程序以及接收和发送页面请求的Web服务器。(请注意,将Windows之类的系统简化为仅运行一个线程甚至仅运行一个进程,这实际上是不可能的。运行Windows Task Manager,您通常会看到一长串活动进程,其中许多进程将运行多个线程。 )

回答您的第二个问题:大多数进程/线程不是CPU约束的(即,不是连续运行且不间断),而是停止并频繁等待I / O完成。在该等待期间,其他进程/线程可以运行,而无需从等待代码中“窃取”(即使在单核计算机上)。


-5

线程是一种抽象,它使您能够编写与操作序列一样简单的代码,没有意识到该代码是与其他代码交错执行的,或者停在等待IO的时间,或者(也许有些了解)在等待其他线程的代码事件或消息。


自降低投票以来,我可能已经通过添加更多示例来对此进行了编辑-但是并未发明线程(或进程,在这种情况下几乎没有区别)以提高性能,而是简化了异步代码并避免编写复杂的状态机必须处理程序中所有可能的超级状态。实际上,即使在大型服务器中,通常也只有一个CPU。我很好奇,为什么我的回答被认为是没有帮助的?
KarlP

-8

关键是绝大多数程序员不了解如何设计状态机。能够将所有内容放入自己的线程中,使程序员不必考虑如何有效地表示不同正在进行的计算的状态,以便可以中断它们并在以后恢复它们。

例如,考虑视频压缩,这是一个非常占用CPU的任务。如果您使用的是gui工具,则可能希望界面保持响应状态(显示进度,响应取消请求,调整窗口大小等)。因此,您可以设计编码器软件来一次处理一个大单元(一个或多个帧),然后在独立于UI的单独线程中运行它。

当然,一旦意识到能够保存进行中的编码状态就很好,因此可以关闭程序以重新启动或玩耗时费力的游戏,您就会意识到应该已经学会了如何从开始。要么,要么您决定设计一个操作系统休眠的全新问题,以便您可以挂起和恢复单个应用程序到磁盘...


7
不(非常!)值得-1,但说真的,这是我听过有人说过的最愚蠢的愚蠢之谈。例如,我在实现状态机方面没有任何问题。一个都没有。我只是不喜欢在有其他工具可以使代码更清晰更易于维护的情况下使用它们。状态机有他们的位置,在那些地方它们是无法匹配的。将CPU密集型操作与GUI更新交织在一起并不是这些地方之一。至少,协程在那里是更好的选择,线程甚至更好。
我的正确观点2010年

对于每个修改我的答案的人来说,这不是反对使用线程的观点!如果您可以编写出色的状态机代码,并确保即使不必这样做,也可以在单独的线程中运行状态机通常是有意义的。我的评论是,使用线程的选择通常主要是出于避免设计状态机的渴望,许多程序员认为状态机“太难了”,而不是出于任何其他好处。
R .. GitHub停止帮助ICE,2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.