Java多线程 - 更少或更少线程做更多的线程?

时间:2015-11-02 19:36:05

标签: java multithreading asynchronous

编辑:这个问题可能也适用于其他语言 - 其背后的整体理论似乎主要与语言无关。但是,由于这将在JVM中运行,因此我确定JVM开销/线程与其他环境之间存在差异。

EDIT 2 :为了更清楚地说明一点,我想主要的问题是哪个更好的可扩展性:拥有更小的线程,可以更快地返回,以便为其他工作负载处理其他工作块,或尝试尽快获得单一工作量?工作负载是连续的,多线程在这种情况下不会帮助加速单个工作单元;它更希望提高整个系统的吞吐量(感谢Uri引导我做出澄清)。

我正在研究一个替换现有系统的系统;当前的系统负载很重,因此我们已经知道更换需要具有高度可扩展性。它与几个外部流程进行通信,例如电子邮件,其他服务,数据库等,我已经计划使其成为多线程以帮助扩展。我之前曾在多线程应用程序上工作过,没有任何高性能/可伸缩性要求,所以在获得绝对最大的并发性时,我没有多少经验。

我的问题是在线程之间划分工作的最佳方法是什么?我正在查看两个不同的版本,一个为整个工作流创建一个线程,另一个为每个步骤创建一个线程,继续下一步(在新的/不同的线程中)上一步完成 - 可能使用NodeJS样式的回调系统,但不是非常关注直接实现细节。

我不太了解多线程的细节 - 例如上下文切换等等 - 所以我不知道多线程的开销是否会影响每个线程的执行时间线程。一方面,与多线程相比,单线程模型似乎对于单个工作流来说是最快的;但是,它也会占用整个工作流程的单个线程,而多线程会更短,并且会更快地返回池中(我想,至少)。

希望潜在的概念很容易理解;这里有一个伪造的伪代码示例:

// Single-thread approach
foo();
bar();
baz();

或者:

// Multiple Thread approach
Thread.run(foo);
when foo.isDone()
    Thread.run(bar);
    when bar.isDone()
         Thread.run(baz);

更新:完全忘了。我考虑多线程方法的原因是(可能是错误的)认为,由于线程的执行时间较短,因此它们可用于整个工作负载的其他实例。如果每个操作花费5秒钟,那么单线程版本将锁定一个线程15秒;多线程版本将锁定单个线程5秒,然后它可以用于另一个进程。

有什么想法吗?如果在互联网中有类似的东西,我甚至不喜欢链接 - 我无法想到如何搜索这个(我周一责备这个,但它可能是明天一样)。

3 个答案:

答案 0 :(得分:2)

多线程不是灵丹妙药。这意味着结束。 在进行任何更改之前,您需要问自己瓶颈在哪里,以及您真正想要并行化的内容。我不确定如果没有更多信息我们可以在这里给出好的建议。

如果foo,bar和baz是管道的一部分,那么您不一定会通过使用多个线程来改善单个序列的整体延迟。

您可以做的是通过让不同输入部件上的多个管道执行并行工作来提高吞吐量,方法是让以后的项目通过管道,而早期项目被阻塞(例如,I) / O)。例如,如果特定输入的bar()被阻止并等待通知,则可能您可以对另一个输入执行计算量大的操作,或者将CPU资源用于foo()。一个特别重要的问题是,任何外部依赖项是否都充当有限的共享资源。例如,如果一个线程正在访问系统X,另一个线程是否会受到影响?

如果你想分裂和克服你的问题,将线程分开并将其解决 - 将输入分成更小的部分,通过管道运行每个部分,然后等待所有部分准备就绪,线程也非常有效。这可以通过您正在查看的工作流程来实现吗?

答案 1 :(得分:1)

如果您需要先执行foo,然后执行bar,然后执行baz,则应该有一个线程按顺序执行这些步骤。这很简单,显而易见。

使用装配线方法最好的最常见情况是将代码保留在缓存中比将数据保留在缓存中更重要。在这种情况下,让一个反复执行foo的线程可以将此步骤的代码保留在缓存中,保持分支预测信息,等等。但是,当您将foo的结果传递给执行bar的线程时,您将遇到数据缓存未命中。

这更复杂,只有在你有充分的理由认为它会更好的时候才会尝试。

答案 2 :(得分:0)

使用单个线程完成整个工作流程。

划分工作流程无法改善一项工作的完成时间:由于工作流程的各个部分必须按顺序完成,因此一次只能有一个线程处理该工作。然而,分解这些阶段可能会延迟一件作品的完成时间,因为可以拿起一件作品的最后一部分的处理器可能会选择另一件作品的第一部分。

将阶段分解为多个线程也不太可能改善完成所有工作的时间,相对于在一个线程中执行所有阶段,因为最终您仍然必须执行所有工作的所有阶段

这是一个例子。如果你有200个这样的工作,每个需要三个5秒的阶段,并且说两个线程在两个处理器上运行,将整个工作流保持在一个线程中会在15秒后产生前两个结果。获得所有结果需要1500秒,但您一次只需要两个工作的工作记忆。如果你打破了阶段,那么获得你的第一个结果可能需要花费超过15秒的时间,如果你仍然希望在1500秒内获得所有结果,你可能需要并行处理所有200件工作的内存

在大多数情况下,将顺序阶段分解为不同的线程没有效率优势,并且可能存在实质性缺点。线程通常仅在您可以使用它们并行工作时才有用,这对您的工作阶段来说似乎并非如此。

然而,将阶段分解为单独的线程存在巨大的缺点。缺点是您现在需要编写管理阶段的多线程代码。在这样的代码中编写bug非常容易,而且在生产部署之前很难捕获这些bug。

避免此类错误的方法是根据您的要求尽可能简化线程代码。对于您的工作阶段,最简单的线程代码完全没有。