在当前的软件行业中,多线程有多重要?[关闭]


59

我有将近3年使用MVC框架(如struts)用Java编写Web应用程序的经验。尽管我已经为大型零售连锁店编写了代码,但到目前为止,我从未编写过多线程代码。

在面试过程中,我会遇到一些有关多线程的问题,我通常会回答它们(大多数是简单的问题)。这让我想知道在当前的行业场景中多线程有多重要?


8
您可能没有明确地这样做,但是您肯定在幕后利用了它。
马丁·约克

1
我很少使用多线程代码来工作,但是我确实尝试阅读它/能够在面试中讨论它。我不想与不获取线程的编码器一起工作,也不想与不在乎其他编码器是否获取线程的编码器一起工作。
约伯

1
我很少在Web开发中使用它,但我认为它在其他地方更为常见。例如,我最近正在编写一个Android应用程序,并且意识到如果您有任何网络活动,则必须使用多线程。
jwegner 2011年

4
重要的不是多线程,而是并行计算。如果您认为发送到您的Web应用程序的所有单个请求都在线程中...您一定在吸烟。
user606723 2011年

1
即使对于单线程编程,“在线程外思考”的能力也非常好。您理所当然地花了很多钱,您的代码通常更健壮和可重用。
corsiKa 2011年

Answers:


92

这是非常重要的。

不过,更重要的是要了解多线程仅仅是解决异步问题的一种方法。现在许多人正在编写软件的技术环境与历史软件开发环境(执行批处理计算的整体应用程序)在两个关键方面有所不同:

  • 现在,多核计算机很常见。我们不再期望时钟速度或晶体管密度增加几个数量级。计算的价格将继续下降,但由于大量的并行性而将下降。我们将必须找到一种利用这种力量的方法。

  • 现在,计算机已高度联网,现代应用程序依赖于能够从各种来源获取丰富信息的能力。

从计算的角度来看,这两个因素本质上可以归结为相同的核心思想:信息将越来越以异步方式获得。所需的信息是在计算机上的另一个芯片上还是在世界另一端的芯片上计算的,都没有关系。无论哪种方式,您的处理器都坐在那里每秒燃烧数十亿个周期,以等待可能正在做有用工作的信息。

因此,现在重要的事情,以及将来甚至更重要的事情,不是多线程本身,而是异步处理。多线程只是做到这一点的一种方法-一种复杂的,容易出错的方法,随着弱内存模型芯片的广泛使用,它只会变得更加复杂,更容易出错。

工具供应商所面临的挑战是,要想出一种比多线程更好的方法来让我们的客户处理将来将要使用的异步基础结构。


5
+1是一个很好的答案,它比我自己的谦虚尝试值得更多的荣誉。
彼得Török

2
信息将越来越以异步方式获得。如果那不是事实。。。
surfasb 2011年

2
concurrencyasynchronous 行为更重要。您可以异步使用,而无需并发(即,一个核心CPU上的多个线程)asynchronous不是语义上的替代concurrency

5
@Jarrod:由于您提到的原因,驯服异步比仅驯服并发更为重要:并发只是一种特别困难的异步。并发的困难部分不是它的“同时发生的事情”方面,实际上,并发通常只是模拟的并发,例如,通过时间切片的非合作式多任务处理。困难的部分是有效地使用资源,而不堵,挂,死锁,也无需编写内而外的,是难以对本地推理程序。
埃里克·利珀特

“并发通常只是模拟并发,例如,通过时间分片进行的非合作式多任务处理”:据我了解,这仍然是(真正的)并发,也许您是说它不是并行性?
Giorgio

46

随着现代处理器具有越来越多的内核,它变得越来越重要。十年前,大多数现有计算机只有一个处理器,因此多线程仅在高端服务器应用程序上很重要。如今,即使是基本的笔记本电脑也都具有多核处理器。在几年甚至移动设备中……因此,需要更多的代码来利用并发的潜在性能优势并在多线程环境中正确运行。


3
+1:比以往任何时候都重要。还要记住,在系统设计中,您还可以通过对工作进行分区来获得多线程的好处,以便更多的进程在执行该工作。
斯科特C威尔逊

11
相当多的移动设备已经具有多核处理器!
Che Jami

3
我认为自从第一个分时系统建立以来,多线程就变得非常重要。具有多个处理器/内核只会为具有多个线程增加效率的新维度。
jwernerny 2011年

也许(尤其是在移动设备上)线程是个坏主意。操作系统可能应该处理内核的优化使用,而不会让有缺陷的用户代码尝试进行线程化。普通用户很少有应用程序可以满足该需求,或者可以从中受益。唯一的例外是(高端图形应用程序/开发人员工具/天气建模/ Web服务器(及相关服务))所有非常高端的专用应用程序。
马丁·约克

1
@ Tux-D,您可能会在使用多个核心的移动设备上玩游戏。这不是什么例外。
whitequark 2011年

28

总的来说,多线程已经非常重要,并且只会在未来几年变得越来越重要(如PéterTörök所指出的)-这就是处理器在可预见的未来将如何扩展(更多的内核而不是更高的MHz) 。

但是,就您而言,您似乎主要是在使用Web应用程序。Web应用程序本质上是多线程的,这是由于Web服务器处理每个用户(即并行)的请求的方式。虽然了解并发性和线程安全性(特别是在处理缓存和其他共享数据时)对您来说可能很重要,但我怀疑您会遇到太多情况,在内部对Web应用程序代码进行多线程化(例如,多个工作程序)会有所帮助每个请求的线程数)。从这个意义上讲,我认为对于Web开发人员而言,真正不需要多线程专家。在面试中经常会问到这个问题,因为这是一个棘手的问题,而且还因为许多面试官在到达那里10分钟之前就提出了几个问题。


+1表示发帖人是Web开发人员,大多数Web服务器容器为您完成了大量的多线程工作。并不是在某些情况下可以消除这种需要,但是99%的时间多线程控制器代码并不是MVC调用最大的性能改进。
Mufasa

19

多线程是一个红鲱鱼。多线程是对实际问题Concurrency的实现细节。并非所有线程程序都是由于锁而并发的,而并非因为锁。

线程只是用于实现concurrent程序的一种模型和实现模式。

例如,您可以编写高度可扩展且具有容错功能的软件,而无需每次都使用诸如Erlang之类的语言进行任何多线程处理。


+1尽管我仍然认为Erlang是多线程的;社区只是将“线程”一词重新定义为依赖于可变的共享状态,从而将其与之区分开。

1
默认情况下,Erlang VM每个CPU使用1个线程,但是作为Erlang开发人员,您只能访问Erlang VM提供的轻量级进程来访问底层OS线程。

10

采访中我遇到了一些有关多线程的问题...

对于通过面试来说,多线程可能非常重要。自言自语:“在面试我们团队的候选人时,我问并发问题不是因为这些技能在我们的项目中很重要(不是),而是因为这些使我更容易评估我们使用的语言的常识……”


2
对多线程和并发编程有一些了解通常也可以转化为一种防御方法,这可能是一件好事。如果您必须考虑到流程中完全不相关的某些内容可能会或可能不会抢占单个逻辑语句并在其他所有内容中执行,那么您必须为这种可能性做好计划。多线程实现(与其他形式的并发相反)仅意味着您要承担额外的负担,即它可能会对非线程本地的任何状态产生影响。
的CVn

6

对于大多数行业和应用程序而言,了解如何利用线程来提高性能是当今软件环境中的一项关键技能。

至少应该了解与并发有关的问题。

显而易见的是,并非所有应用程序或环境都将能够利用它,例如在许多嵌入式系统中。但是,似乎Atom处理器(等等)似乎正在努力改变它(轻型多核开始变得越来越普遍)。


4

听起来您已经在编写多线程代码。

大多数Java Web应用程序可以同时处理多个请求,并且它们使用多个线程来处理。

因此,我要说,至少要了解基础知识很重要。


18
<挑剔>显然他(她)是不是写多线程代码,这是在多线程环境中运行只(单线程)代码</挑剔>。
彼得Török

2

在您需要它的情况下,它仍然很重要,但是就像开发中的许多事物一样,它是完成正确工作的正确工具。我花了三年没有碰线程,现在我所做的几乎所有事情都有其根据。对于多核处理器,仍然非常需要线程,但是所有传统原因仍然有效,您仍然需要响应式接口,并且仍然希望能够处理同步并立即进行其他处理。


2

简短的回答:非常。

更长的答案:电子(基于晶体管的)计算机正在迅速接近技术的物理极限。在管理热量产生和微观电路的量子效应(电路路径已经在现代芯片上如此紧密地放置在一起,以至于被称为“量子隧穿”的效应可以使电子产生)的同时,越来越难以从每个内核中挤出更多的时钟。无需将传统电弧的适当条件“从轨道上跳到另一电路”;因此,实际上,所有芯片制造商都在通过在每个CPU中放置更多的“执行单元”来专注于使每个时钟能够执行更多的工作。然后,计算机可以每个时钟执行2或4甚至8个操作,而不是计算机每个时钟仅执行一项操作,而Intel具有“ HyperThreading”功能,基本上将一个CPU内核拆分为两个逻辑处理器(有一些限制)。几乎所有制造商都将至少两个单独的CPU内核放入一个CPU芯片中,而当前台式机CPU的黄金标准是每个芯片四个内核。当使用两个CPU芯片时,可能有八个。有为“四核”处理器设计的服务器主板(16个EU加可选的HT),下一代CPU可能每个芯片有六个或八个。

所有这些的结果是,要充分利用计算机获得计算能力的方式,您必须能够允许计算机“分而治之”您的程序。托管语言至少具有一个GC线程,该线程与程序分开处理内存管理。有些还具有处理COM / OLE互操作的“转换”线程(与保护托管“沙盒”和性能一样多)。但是,除此之外,您还必须真正开始思考程序如何同时执行多项操作,并以允许异步处理程序段的功能来构造程序。Windows和Windows用户实际上希望您的程序在后台线程中执行长时间,复杂的任务,这样可以使程序的UI(在程序的主线程中运行)对Windows消息循环“响应”。显然,具有可并行化解决方案(例如排序)的问题是自然的候选者,但是受益于并行化的问题种类有限。


1

只是关于多线程的警告:更多线程并不意味着更高的效率。如果管理不当,它们可能会降低系统速度。Scala的参与者改进了Java的线程并最大限度地提高了系统使用率(因为您是Java开发人员,所以提到了它)。

编辑: 这是多线程的缺点要记​​住的一些事情:

  • 共享硬件资源时线程之间的相互干扰
  • 即使仅执行一个线程,单个线程的执行时间也不会得到改善,但会降低。这是由于较慢的频率和/或容纳线程交换硬件所必需的附加管道级。
  • 软件对多线程的硬件支持更加明显,因此与多处理相比,需要对应用程序和操作系统进行更多更改。
  • 并发管理困难。
  • 测试困难。

同样,此链接可能对相同有一定帮助。


2
这似乎并不回答OP的问题: - /
彼得Török

但是,它提供了线程的顶层(最顶层)视图。在研究多线程之前要考虑的一件事。
2011年

@ c0da Stack Exchange不是讨论区:答案应该直接回答问题。您能否扩大答案,使它返回到问询者想要的东西?

1

这让我想知道在当前行业场景中多线程处理有多重要?

在性能至关重要的领域中,性能不是来自第三方代码的繁琐工作,而是我们自己的性能,因此我倾向于从CPU的角度考虑重要性的顺序(GPU是我赢得的通配符)。不参加):

  1. 内存效率(例如:参考位置)。
  2. 算法化
  3. 多线程
  4. SIMD
  5. 其他优化(例如静态分支预测提示)

请注意,此列表不仅仅基于重要性,还包括许多其他动态因素,例如它们对维护的影响,它们的直接程度(如果不是,则需要提前考虑),它们与列表中其他成员的交互作用等。

记忆效率

大多数人可能会对我选择的内存效率超过算法感到惊讶。这是因为内存效率与该列表上的所有其他4个项目相互作用,并且是因为通常在“设计”类别中而不是在“实现”类别中对此进行考虑。诚然,这里存在一些鸡或蛋的问题,因为了解内存效率通常需要考虑列表中的所有4个项目,而所有其他4个项目也都需要考虑内存效率。但这是一切的核心。

例如,如果我们需要一种数据结构,该结构提供线性时间顺序访问和向后固定插入时间,而对于小元素则没有其他选择,那么此处要达到的天真的选择将是链表。这不考虑内存效率。当我们考虑混合中的内存效率时,在这种情况下我们最终会选择更多连续的结构,例如基于可增长的基于数组的结构或更多连续的节点(例如,一个节点中存储128个元素)链接在一起,或者至少由池分配器支持的链表。尽管具有相同的算法复杂度,但它们仍具有显着优势。同样,尽管算法复杂度较低,但由于存储效率的原因,我们经常选择数组的快速排序而不是合并排序。

同样,如果我们的内存访问模式本质上如此细粒度和分散,以至于我们最终最大化了错误共享的数量,同时又将代码锁定在最细粒度的级别,那么我们就无法拥有高效的多线程。因此,内存效率乘以效率多线程。这是充分利用线程的前提。

列表上方的每个项目都与数据进行复杂的交互,因此,专注于数据表示方式最终取决于内存效率。上面这些中的每一个都可能因表示或访问数据的不适当方式而成为瓶颈。

内存效率如此重要的另一个原因是,它可以应用于整个代码库。通常,当人们认为低效率是从零散的工作中累积的,这表明他们需要抓住探查器。然而,即使在进行性能分析后,低延迟字段或处理非常有限的硬件的字段实际上仍会发现会话,这些会话表明在代码库中没有明确的热点(时间分散在整个地方),而代码库的分配,复制和使用方式公然低效访问内存。通常,这是整个代码库中唯一一次可能出现性能问题,这可能会导致在整个代码库中应用一套全新的标准,而内存效率通常是其核心。

算法化

这几乎是给定的,因为排序算法中的选择可以使大量的输入(需要数月的排序时间和数秒的排序时间)有所不同。如果选择在真正低于标准的二次或三次算法与线性算法之间,或者在线性与对数算法或常数之间进行选择,那么它将产生最大的影响,至少直到我们拥有一百万台核心机器(在这种情况下,内存为效率将变得更加重要)。

但是,它不在我的个人列表的顶部,因为该领域的任何人都知道会使用加速结构进行视锥剔除,例如,我们对算法的知识非常了解,并且知道使用诸如trie的变体之类的东西,例如用于基于前缀的搜索的基数树是婴儿的东西。缺乏我们正在研究的领域的这种基础知识,那么算法效率肯定会上升到最高,但是算法效率通常是微不足道的。

在某些领域,还必须发明新的算法(例如:在网格处理中,我不得不发明数百种,因为它们以前不存在,或者其他产品中类似功能的实现是专有秘密,未在论文中公开) )。但是,一旦我们解决了问题,找到了获得正确结果的方法,而一旦效率成为目标,那么真正获得收益的唯一方法就是考虑我们如何与数据(内存)进行交互。如果不了解内存效率,那么新算法可能会不必要地变得复杂,而徒劳地努力使其变得更快,而它所需要的只是稍微考虑一下内存效率以产生一个更简单,更优雅的算法。

最后,算法倾向于更多地属于“实现”类别而不是内存效率。即使使用最初使用的次优算法,通常也更容易改进事后分析。例如,劣等图像处理算法通常只是在代码库的一个本地位置实现。以后可以将其换成更好的。但是,如果所有图像处理算法都绑定到Pixel具有次优内存表示的接口,但是纠正该接口的唯一方法是更改​​表示多个像素(而不是单个像素)的方式,那么我们经常SOL,并且必须完全将代码库改写为Image接口。替换排序算法的方法也一样-通常是实现细节,而对要排序的数据的基本表示形式或通过消息传递的方式的完整更改可能需要重新设计接口。

多线程

在性能方面,多线程是一项艰巨的任务,因为它是一种针对硬件特性的微级优化,但是我们的硬件确实朝着这个方向扩展。我已经有拥有32个核心的同龄人(我只有4个核心)。

但是,如果目的是为了加速软件,则多线程处理是专业人员可能知道的最危险的微优化之一。竞态条件几乎是最致命的错误,因为它本质上是不确定性的(可能在调试环境之外的最不方便的时间,每隔几个月才在开发人员的机器上显示一次,如果有的话)。因此,在所有这些方面,可以说对代码的可维护性和潜在正确性造成的负面影响最大,特别是因为与多线程相关的错误即使在最仔细的测试下也很容易就飞走。

然而,它变得如此重要。考虑到我们现在拥有的内核数量,它可能仍然不总是比内存效率(有时可以使事情快一百倍)之类的东西更重要,但我们看到的内核越来越多。当然,即使使用100核计算机,我仍然将内存效率放在首位,因为没有它通常是不可能实现线程效率的。程序可以在这样的机器上使用一百个线程,但仍然很慢,缺乏有效的内存表示和访问模式(这将与锁定模式相关联)。

SIMD

SIMD也有点尴尬,因为寄存器实际上正在变宽,并计划进一步变宽。最初,我们看到64位MMX寄存器,然后是能够并行执行4个SPFP操作的128位XMM寄存器。现在我们看到256位YMM寄存器能够并行处理8个。并且已经有针对512位寄存器的计划,这将允许16个并行。

这些将交互并与多线程的效率相乘。然而,SIMD会像多线程一样降低可维护性。尽管与它们相关的错误不一定像死锁或竞争条件那样难于再现和修复,但可移植性却很尴尬,确保代码可以在每个人的机器上运行(并根据其硬件功能使用适当的指令)是尴尬。

另一件事是,尽管当今的编译器通常不会击败专家编写的SIMD代码,但它们确实可以轻松击败幼稚的尝试。它们可能会改进到我们不再需要手动执行的程度,或者至少无需手动编写内在代码或直接汇编代码即可(也许只是一些人工指导)。

同样,如果没有有效进行矢量化处理的内存布局,SIMD毫无用处。我们最终将只将一个标量字段加载到一个宽寄存器中,然后对其进行一次操作。所有这些项目的核心是对内存布局的依赖,以使其真正有效。

其他优化

这些通常是我现在建议我们开始使用的“微型”,如果这个词不仅暗示着超越算法重点,而且还朝着对性能影响很小的变化。

通常,尝试针对分支预测进行优化需要更改算法或内存效率,例如,如果仅通过提示并重新排列静态预测代码来尝试这样做,则只会提高此类代码的首次执行效率,如果常常不能完全忽略不计。

返回多线程性能

那么,无论如何,从性能上下文来看多线程有多重要?在我的4核计算机上,理想情况下,它可使处理速度提高约5倍(超线程可以达到的速度)。对于拥有32个核心的我的同事而言,这将更为重要。在未来的几年中它将变得越来越重要。

所以这很重要。但是,如果没有足够的内存效率以允许少量使用锁,减少错误共享等,仅抛出一堆线程是没有用的。

性能之外的多线程

从直接吞吐量的意义上讲,多线程并不总是与纯粹的性能有关。有时,它甚至可以以可能的吞吐量成本来平衡负载,以提高对用户的响应速度,或者允许用户执行更多的多任务处理而不必等待事情完成(例如:在下载文件的同时继续浏览)。

在这种情况下,我建议多线程向顶端上升(甚至可能超过内存效率),因为那是关于用户端设计,而不是充分利用硬件。在这种情况下,它将经常主导界面设计以及我们构建整个代码库的方式。

当我们不只是并行化一个访问大规模数据结构的紧密循环时,多线程进入了真正的“设计”类别,而设计总是比实现更为重要。

因此,在这种情况下,我想说考虑多线程是绝对关键的,甚至比内存表示和访问更重要。


0

并行和并行编程变得越来越重要。线程只是同时执行多项操作的一种编程模型(而不是像在多核处理器兴起之前那样伪并行)。由于线程共享许多资源,程序员负责使它们协作,因此(IMHO相当)批评多线程是复杂和危险的。否则,您将陷入难以调试的死锁。


0

由于我们可能需要联系许多外部应用程序,因此可能会发生一些后台过程,其中外部系统交互会花费更多时间,并且最终用户无法等待该过程完成。所以多线程很重要。

我们正在应用程序中使用该代码,我们首先尝试联系外部系统(如果它已关闭),然后将请求保存在数据库中,并跨越一个线程以在backgound中完成该过程。也可能在批处理操作中需要。


0

从历史上看,人们不得不手工完成多线程编程。他们必须直接使用所有核心组件(线程,信号量,互斥体,锁等)。

所有这些努力使得应用程序能够通过向单个系统添加额外的cpus进行扩展。这种垂直可扩展性受到“我能买到的最大的服务器”的限制。

如今,我看到了朝着使用更多框架和不同设计模型进行软件设计的转变。MapReduce就是这样一种模型,它专注于批处理。

目标是水平扩展。添加更多标准服务器,而不是购买更大的服务器。

话虽如此,但真正了解多线程编程仍然非常重要。我一直处于这样的情况,有人创建了竞争条件,甚至不知道竞争条件是什么,直到我们在测试过程中注意到奇怪的错误。


-1

我的机器有8个核心。在任务管理器中,我有60个进程正在运行。有些像VS,最多使用98个线程。Outlook使用26。我希望我的大部分内存使用情况是分配给每个空闲线程的堆栈。

我个人正在等待300核计算机问世,这样我就不必等待Outlook做出响应。当然,届时Outlook将使用301个线程。

仅当您要构建在特定时间计算机上唯一重要的系统(例如计算引擎)时,多线程才有意义。桌面应用程序可能会因为耗尽所有可用内核对用户有所帮助。使用请求/响应模型的Web应用程序本质上是多线程的。

对于框架和语言设计人员以及后端系统程序员而言,这很重要-对应用程序构建者而言并不重要。但是,了解一些基本概念(例如锁定和编写异步代码)可能是值得的。


我经常在后台线程上重击某些东西,例如长数据库负载,但是这种情况很少见,我必须处理竞争条件或锁等(实际上可能永远不会)
Aran Mulholland
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.