multithreading:比内核multithreading的点是什么?
我认为多核心计算机的意义在于它可以同时运行多个线程。 在这种情况下,如果你有一个四核机器,一次运行4个以上的线程有什么意义? 难道他们不是在偷对方的时间吗?
仅仅因为一个线程的存在并不总是意味着它正在运行。 线程的许多应用程序都涉及一些线程进入hibernate状态,直到他们需要做某些事情 – 例如,用户input触发线程唤醒,进行一些处理,然后重新进入hibernate状态。
本质上,线程是可以独立运行的独立任务,不需要了解其他任务的进度。 有可能有更多这些比你有能力同时运行; 即使他们有时需要排队等候,他们仍然很方便。
答案围绕线程的目的进行,这是并行的:一次运行几条独立的执行线。 在一个“理想”的系统中,每个核心执行一个线程:不中断。 实际上情况并非如此。 即使你有四个内核和四个工作线程,你的进程和它的线程也会不断被切换到其他进程和线程。 如果你正在运行任何现代操作系统,每个进程至less有一个线程,许多人有更多。 所有这些进程一次运行。 你现在可能有几百个线程都在你的机器上运行。 你永远不会有一个线程运行的情况下,没有时间“偷”。 (好吧,如果你正在使用实时操作系统,或者即使在Windows上,也可以使用实时线程优先级,但实际上你可能会遇到这种情况。
以此为背景,答案是肯定的,一个真正的四核机器上的四个以上的线程可能会给你一个'偷取彼此的时间'的情况, 但是只有每个单独的线程需要100%的CPU 。 如果一个线程不能工作100%(因为UI线程可能不是,或者一个线程做了less量的工作或者等待其他的线程),那么正在调度的另一个线程实际上是一个好的情况。
实际上比这更复杂:
-
如果你有五分之一的工作需要一次完成呢? 同时运行它们比运行其中的四个更有意义,然后运行第五个。
-
一个线程真的需要100%的CPU是很less见的。 例如,当它使用磁盘或networkingI / O时,可能会花费时间等待没有任何用处。 这是一个非常普遍的情况。
-
如果你有需要运行的工作,一个共同的机制是使用一个线程池。 拥有与核心相同数量的线程似乎有意义,但.Net线程池每个处理器最多有250个线程可用 。 我不确定他们为什么要这样做,但我的猜测是要处理在线程上运行的任务的大小。
所以:偷时间并不是一件坏事(也不是真正的盗窃行为,也就是说系统应该如何工作)。根据线程将要做的工作types来编写你的multithreading程序,这可能不是CPU -界。 根据configuration文件和度量来计算所需的线程数。 你可能会发现从任务或工作而不是线索的angular度思考更有用:写下工作对象,并把它们交给一个池来运行。 最后,除非你的程序是真正关键性能的,否则不要太担心:)
重点是,尽pipe在线程数超过核心数时没有得到任何真正的加速,但是可以使用线程来解开不应该相互依赖的逻辑块。
在一个中等复杂的应用程序中,使用单个线程尽可能快地完成所有代码的“stream”散列。 单线程花费大部分时间来轮询,检查,根据需要有条件地调用例程,很难看到任何东西,但只是一个细节的沼泽。
将这种情况与您可以将线程专用于任务的情况对比,从而查看任何单个线程,您可以看到该线程在做什么。 例如,一个线程可能会阻塞等待来自套接字的input,将该streamparsing为消息,过滤消息,并在有效消息出现时将其传递给其他工作线程。 工作者线程可以处理来自许多其他来源的input。 每个代码都将显示一个干净的,有目的的stream程,而不必进行明确的检查,以确保没有别的事情可做。
通过这种方式对工作进行分区,您的应用程序可以依靠操作系统来安排CPU的下一步工作,因此您不必在应用程序的任何位置进行明确的条件检查,以了解哪些内容可能会被阻塞,哪些内容可以处理。
如果一个线程正在等待一个资源(例如从RAM加载一个值到一个寄存器,磁盘I / O,networking访问,启动一个新进程,查询一个数据库,或者等待用户input),那么处理器可以工作不同的线程,并在资源可用时返回到第一个线程。 这样可以减lessCPU花费闲置的时间,因为CPU可以执行数百万次操作,而不是闲置。
考虑一个需要从硬盘读取数据的线程。 在2014年,一个典型的处理器内核工作在2.5 GHz,可能能够执行每个周期4条指令。 循环时间为0.4 ns,处理器可以每纳秒执行10条指令。 典型的机械硬盘寻道时间约为10毫秒,处理器能够在从硬盘读取数据所需的时间内执行1亿条指令。 对于具有小caching(4 MBcaching)的硬盘驱动器和具有几GB存储容量的混合驱动器,性能可能会有显着提升,因为连续读取或从混合部分读取的数据延迟可能会快几个数量级。
处理器核心可以在线程之间切换(暂停和恢复线程的开销约为100个时钟周期),而第一个线程等待高延迟input(任何东西比寄存器(1个时钟)和RAM(5纳秒)更贵)包括磁盘I / O,networking访问(250毫秒的延迟),从CD或慢速总线读取数据,或数据库调用。 拥有比内核更多的线程意味着有用的工作可以在解决高延迟任务时完成。
CPU有一个线程调度程序,为每个线程分配优先级,并允许一个线程hibernate,然后在预定时间后恢复。 线程调度器的工作是减less抖动,如果每个线程在再次进入睡眠状态之前只执行了100条指令,就会发生抖动。 切换线程的开销会降低处理器内核的总有用吞吐量。
出于这个原因,你可能想把你的问题分解成合理数量的线程。 如果您正在编写代码来执行matrix乘法,则在输出matrix中为每个单元格创build一个线程可能会过多,而输出matrix中的每行或每n行一个线程可能会降低创build,暂停和恢复线程的开销成本。
这也是分支预测很重要的原因。 如果你有一个if语句需要从RAM加载一个值,但是if和else语句的主体使用已经加载到寄存器的值,处理器可以在条件被评估之前执行一个或两个分支。 一旦条件返回,处理器将应用相应分支的结果并丢弃另一个分支。 在这里执行可能无用的工作可能比切换到不同的线程更好,这可能导致颠簸。
随着我们从高时钟速度的单核处理器转向多核处理器,芯片devise专注于为每个pipe芯增加更多内核,改善内核之间的片上资源共享,更好的分支预测algorithm,更好的线程切换开销,和更好的线程调度。
尽pipe您可以使用线程来加速计算,但是这取决于您的硬件,但主要用途之一是出于用户友好的原因,一次只能执行一个以上的操作。
例如,如果您必须在后台执行一些处理,并且还保持对UIinput的响应,则可以使用线程。 如果没有线程,用户界面会在每次尝试执行任何重大处理时挂起。
也看到这个相关的问题: 线程的实际用途
我强烈反对@ kyoryu的说法,理想的数字是每个CPU一个线程。
想想这样:为什么我们有多处理操作系统? 对于大多数电脑历史来说,几乎所有的电脑都有一个CPU。 然而,从20世纪60年代开始,所有“真正的”计算机都具有多处理(又称多任务)操作系统。
您运行多个程序,以便可以运行多个程序,而其他人则可以运行,例如IO。
让我们抛开关于NT之前的Windows版本是多任务的争论。 从那以后,每个真正的操作系统都有多任务处理。 有些不会将它暴露给用户,但是无论如何,它都可以用于收听手机收音机,与GPS芯片通话,接受鼠标input等。
线程只是更高效的任务。 任务,进程和线程之间没有根本的区别。
一个CPU是一个可怕的东西浪费,所以有很多东西准备好使用它,当你可以。
我会同意用大多数过程语言,C,C ++,Java等编写适当的线程安全代码是很多工作。 现在市场上有6个核心的CPU,还有16个核心的CPU不远,我预计人们会摆脱这些旧的语言,因为multithreading越来越成为一个关键的要求。
与@kyoryu分歧只是恕我直言,其余的是事实。
设想一个Web服务器必须提供任意数量的请求。 您必须并行处理请求,因为否则每个新请求都必须等到所有其他请求完成(包括通过Internet发送响应)。 在这种情况下,大多数Web服务器的核心数比通常服务的请求数less。
这也使服务器的开发人员更容易:您只需编写一个线程程序来处理请求,您不必考虑存储多个请求,服务它们的顺序等等。
上面的大部分答案都是关于性能和同时操作的。 我将从另一个angular度来看待这个问题。
我们来看一个简单的terminal仿真程序。 你必须做以下的事情:
- 监视来自远程系统的传入字符并显示它们
- 注意来自键盘的东西并将它们发送到远程系统
(真正的terminal模拟器可以做更多的事情,包括潜在地回显你input到显示器上的东西,但是现在我们会把这些东西传递下去。)
现在从远程读取的循环很简单,按照以下伪代码:
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资源。
当然,现实世界的使用比上面更复杂。 但是,随着您对应用程序增加更多的关注,集成环路的复杂性呈指数级增长。 逻辑变得更加分散,你必须开始使用诸如状态机,协程等技术来使事情变得易于pipe理。 可pipe理,但不可读。 线程保持代码更易读。
那么为什么你不使用线程?
那么,如果你的任务是CPU绑定的而不是I / O绑定的,那么线程实际上会降低你的系统速度。 性能将受到影响。 很多情况下,很多。 (如果删除了太多的CPU绑定的线程,“Thrashing”是一个常见的问题,你会花费更多的时间来改变活动的线程,而不是运行线程本身的内容。)另外,上述逻辑的一个原因是很简单的是我非常刻意地select了一个简单的(不现实的)例子。 如果你想回应什么键入到屏幕上,那么当你引入共享资源的locking时,你已经有了一个新的世界。 只有一个共享资源,这不是一个问题,但是由于您有更多的资源可以共享,因此它开始成为一个越来越大的问题。
所以最后,线程是关于很多事情的。 例如,就像一些人已经说过的那样,使得I / O绑定的stream程更具响应性(即使总体效率较低)。 这也是使逻辑更容易遵循(但只有当你最小化共享状态)。 这涉及到很多东西,你必须决定它的优点是否超过它的缺点,在个案的基础上。
线程可以帮助用户界面应用程序的响应。 此外,您可以使用线程来获得更多的核心工作。 例如,在一个单核上,可以有一个线程在执行IO,另一个在执行一些计算。 如果它是单线程的,那么核心可能基本上处于空闲状态,等待IO完成。 这是一个相当高的级别的例子,但线程肯定可以用来狠狠地砸你的CPU。
处理器或CPU是插入系统的物理芯片。 一个处理器可以有多个核心(核心是能够执行指令的芯片的一部分)。 如果一个内核能够同时执行多个线程(一个线程是一个单一的指令序列),那么操作系统内核可能会出现多个虚拟处理器。
进程是应用程序的另一个名称。 一般来说,过程是相互独立的。 如果一个进程死了,它不会导致另一个进程死亡。 进程可以进行通信或共享资源,如内存或I / O。
每个进程都有一个单独的地址空间和堆栈。 一个进程可以包含多个线程,每个线程都可以同时执行指令。 进程中的所有线程共享相同的地址空间,但每个线程都有自己的堆栈。
希望这些定义和进一步的研究使用这些基础知识将有助于你的理解。
许multithreading将睡着,等待用户input,I / O和其他事件。
线程的理想用法实际上是每个核心一个。
但是,除非您专门使用asynchronous/非阻塞IO,否则很可能会在某个时刻在IO上阻塞线程,而这些线程不会使用您的CPU。
而且,典型的编程语言使得每个CPU使用1个线程有点困难。 围绕并发(如Erlang)devise的语言可以使不使用额外的线程变得更容易。
一些API的devise方式,你别无select,只能在一个单独的线程(任何阻塞操作)运行它们。 一个例子是Python的HTTP库(AFAIK)。
通常这并不是什么大问题(如果是问题,操作系统或API应该提供一个替代的asynchronous操作模式,即: select(2)
),因为这可能意味着线程将在hibernate期间等待I / O完成。 另一方面,如果某事正在进行繁重的计算, 则必须将其放在一个单独的线程中,而不是说GUI线程(除非您喜欢手动复用)。
为了回应您的第一个猜想:多核机器可以同时运行多个进程,而不仅仅是单个进程的多个线程。
回答你的第一个问题:multithreading的重点通常是在一个应用程序中同时执行多个任务。 网上的典型例子是发送和接收邮件的电子邮件程序,以及接收和发送页面请求的networking服务器。 (请注意,将Windows系统简化为仅运行一个线程,甚至只运行一个进程本质上是不可能的。运行Windows任务pipe理器,通常会看到一长串活动进程,其中许多进程将运行多个线程。 )
在回答第二个问题时:大多数进程/线程不是CPU限制的(即,没有连续运行和不间断运行),而是经常停止并等待I / O完成。 在等待期间,其他进程/线程可以在等待代码(即使在单个核心机器上)中“偷”的情况下运行。
我知道这是一个有着很多好答案的超级老问题,但我在这里指出一些在当前环境中很重要的东西:
如果你想devise一个multithreading应用程序,你不应该devise一个特定的硬件设置。 CPU技术多年来一直在飞速发展,核心数量稳步增加。 如果您故意将应用程序devise为仅使用4个线程,那么您可能会将自己限制在八核心系统中(例如)。 现在,即使是20核心的系统也是可以买到的,所以这样的devise肯定是弊大于利。
线程是一种抽象,使您能够像编写一系列操作一样简单地编写代码,而不知道代码是否与其他代码交错执行。
关键是绝大多数程序员不懂如何devise一个状态机。 能够将所有内容放在自己的线程中,使程序员不必考虑如何有效地表示不同进行中计算的状态,以便可以中断和稍后恢复。
举一个例子,考虑video压缩,这是一个非常cpu密集型的任务。 如果您使用的是gui工具,您可能希望界面保持响应(显示进度,响应取消请求,调整窗口大小等)。 因此,您可以devise编码器软件,以一次处理大型设备(一个或多个帧),并在自己的线程中运行,与UI分离。
当然,一旦你意识到能够保存进行中的编码状态是很好的,所以你可以closures程序来重新启动或者玩一个资源匮乏的游戏,你意识到你应该已经学会了如何devise状态机开始。 要么,要么你决定devise一个全新的进程hibernate的问题你的操作系统,所以你可以暂停和恢复单个应用程序到磁盘…