【发布时间】:2011-07-13 19:04:57
【问题描述】:
我有很多工作(数千个工作)需要 Scala 应用程序处理。每件作品都是一个 100 MB 文件的文件名。要处理每个文件,我需要使用一个非线程安全的提取器对象(我可以有多个副本,但副本很昂贵,而且我不应该为每个作业制作一个)。在 Scala 中并行完成这项工作的最佳方式是什么?
【问题讨论】:
标签: scala concurrency parallel-processing
我有很多工作(数千个工作)需要 Scala 应用程序处理。每件作品都是一个 100 MB 文件的文件名。要处理每个文件,我需要使用一个非线程安全的提取器对象(我可以有多个副本,但副本很昂贵,而且我不应该为每个作业制作一个)。在 Scala 中并行完成这项工作的最佳方式是什么?
【问题讨论】:
标签: scala concurrency parallel-processing
您可以将提取器包装在 Actor 中,并将每个文件名作为消息发送给 Actor。由于参与者的实例一次只处理一条消息,因此线程安全不会成为问题。如果您想使用多个提取器,只需启动多个 Actor 实例并在它们之间进行平衡(您可以编写另一个 Actor 来充当负载均衡器)。
然后提取器参与者可以将提取的文件发送给其他参与者以并行执行其余的处理。
【讨论】:
不要制作 1000 个作业,而是制作 4x250 个作业(针对 4 个线程)并为每批分配一个提取器。在每个批次中,按顺序工作。这可能不是最佳的并行方式,因为一批可能会更早完成,但它很容易实现。
可能正确(但更复杂)的解决方案是创建一个提取器池,作业从中取出提取器并在完成后将它们放回原处。
【讨论】:
我会创建一个线程池,其中每个线程都有一个提取器类的实例,并实例化尽可能多的线程以使系统饱和(基于 CPU 使用率、IO 带宽、内存带宽、网络带宽,争用其他共享资源等)。然后使用线程安全的工作队列,这些线程可以从中提取任务、处理它们并迭代直到容器为空。
请注意,几乎任何一种现代语言都应该有一个或多个库可以完全实现这一点。在 C++ 中,它将是英特尔的线程构建块。在 Objective-C 中,它将是 Grand Central Dispatch。
【讨论】:
这取决于:提取器为每个作业消耗的相对 CPU 量是多少?
如果它非常小,您就有一个经典的单生产者/多消费者问题,您可以找到许多不同语言的解决方案。对于 Scala,如果你不愿意开始使用 Actor,你仍然可以使用 Java API(Runnable、Executors 和 BlockingQueue,都很好)。
如果数量很大(超过 10%),您的应用将永远无法使用多线程模型进行扩展(请参阅 Amdhal 定律)。您可能更喜欢运行多个进程(多个 JVM)以获得线程安全,从而消除非顺序部分。
【讨论】:
第一个问题:需要多快完成工作?
第二个问题:这项工作是否会被隔离到单个物理盒子中,或者您的计算资源上限是多少。
第三个问题:需要对每个单独的“作业”执行的工作是否需要阻塞,它是序列化的还是可以分割成并行的工作包?
也许可以考虑一个分布式模型,通过这种模型,您可以通过设计扩展多个节点,从第一个实例、actors、remoteref 首先推出所有这些废话......尝试保持您的逻辑简单易行 - 如此序列化。不要只考虑一个盒子。
这里的大多数答案似乎都集中在产生线程池和执行程序以及所有这些东西的复杂性上——这很好,但在开始用大量思考使生活复杂化之前,请确保你首先处理了真正的问题围绕您如何管理同步逻辑。
如果一个问题可以分解,那就分解它。不要为了这样做而将其过度复杂化 - 它会带来更好的工程代码和更少的不眠之夜。
【讨论】: