【问题标题】:Process work in parallel with non-threadsafe function in scala进程与scala中的非线程安全函数并行工作
【发布时间】:2011-07-13 19:04:57
【问题描述】:

我有很多工作(数千个工作)需要 Scala 应用程序处理。每件作品都是一个 100 MB 文件的文件名。要处理每个文件,我需要使用一个非线程安全的提取器对象(我可以有多个副本,但副本很昂贵,而且我不应该为每个作业制作一个)。在 Scala 中并行完成这项工作的最佳方式是什么?

【问题讨论】:

    标签: scala concurrency parallel-processing


    【解决方案1】:

    您可以将提取器包装在 Actor 中,并将每个文件名作为消息发送给 Actor。由于参与者的实例一次只处理一条消息,因此线程安全不会成为问题。如果您想使用多个提取器,只需启动多个 Actor 实例并在它们之间进行平衡(您可以编写另一个 Actor 来充当负载均衡器)。

    然后提取器参与者可以将提取的文件发送给其他参与者以并行执行其余的处理。

    【讨论】:

    • 我被scala-lang.org/node/242弄糊涂了,关于在使用Actor时如何处理线程数。由于我没有任何阻塞消息传递,我的理解是我不能使用 Actor 的线程池。所以我应该创建多个actor实例,对工作进行分区,然后在它们之间发送出去?
    • 我会创建一个单一的演员来阻止和解雇我所有的工作吗?创建的线程数是否会以预设大小最大?或者我应该有多个(每个核心一个)基于事件的演员,然后向他们开火?
    • 如果你只有一个actor,应用基本上是单线程的。每个内核都有一个可能是最好的选择。
    【解决方案2】:

    不要制作 1000 个作业,而是制作 4x250 个作业(针对 4 个线程)并为每批分配一个提取器。在每个批次中,按顺序工作。这可能不是最佳的并行方式,因为一批可能会更早完成,但它很容易实现。

    可能正确(但更复杂)的解决方案是创建一个提取器池,作业从中取出提取器并在完成后将它们放回原处。

    【讨论】:

    • 使用 SynchromizedQueue (scala-lang.org/api/current/scala/collection/mutable/…) 相当容易。
    • 使用复杂的解决方案还是快速解决方案取决于问题。
    • 我还发现,在 Scala 中,您可以通过使用并行集合 (mycollection.par.foreach) 轻松创建使用 SynchronizedQueue 的线程来进行并行化。
    【解决方案3】:

    我会创建一个线程池,其中每个线程都有一个提取器类的实例,并实例化尽可能多的线程以使系统饱和(基于 CPU 使用率、IO 带宽、内存带宽、网络带宽,争用其他共享资源等)。然后使用线程安全的工作队列,这些线程可以从中提取任务、处理它们并迭代直到容器为空。

    请注意,几乎任何一种现代语言都应该有一个或多个库可以完全实现这一点。在 C++ 中,它将是英特尔的线程构建块。在 Objective-C 中,它将是 Grand Central Dispatch。

    【讨论】:

    • 对于 Scala 中最简单的并发用法,已经存在一个线程池。最好为这个线程池的每个线程创建一个提取器。也许使用静态方法...
    • 我指的是 2.9.0 中并行集合使用的工作线程。看看 scala.collection.parallel 中的类;但我不知道实际使用的线程池来自哪里。我的第一条评论的意思是:也许你可以创建一个池/工厂,根据请求线程提供提取器,然后简单地使用并行集合。他们提供线程,您为每个线程提供一个提取器。
    【解决方案4】:

    这取决于:提取器为每个作业消耗的相对 CPU 量是多少?

    1. 如果它非常小,您就有一个经典的单生产者/多消费者问题,您可以找到许多不同语言的解决方案。对于 Scala,如果你不愿意开始使用 Actor,你仍然可以使用 Java API(Runnable、Executors 和 BlockingQueue,都很好)。

    2. 如果数量很大(超过 10%),您的应用将永远无法使用多线程模型进行扩展(请参阅 Amdhal 定律)。您可能更喜欢运行多个进程(多个 JVM)以获得线程安全,从而消除非顺序部分。

    【讨论】:

    • 这对我来说没有意义,似乎也不是阿姆达尔定律所规定的。法律规定,在运行不可并行部分代码所需的时间之后,您不能通过并行化来加速程序。
    • 阿姆达尔定律说,你获得的加速受到连续时间比例的限制。如果您的提取器不是线程安全的,那么它是顺序代码。如果每个作业占用 20% 的时间,那么 20% 的代码执行时间将是连续的。也就是说,使用无限多的处理器,您将获得 5 倍的加速!使用四核,您最多只能获得 2.5 倍的加速......
    • 对象不是线程安全的,所以需要创建多个实例。并行化就是为了加快提取速度。例如,看看 Novelocrat 的回答。
    • 另外,线程安全的类也是顺序代码,它只是以一种可以安全地在多个线程中使用的方式编写。
    • 我以为您在问题中说过,每个工作不能有一个提取器。因此,即使您设法拥有多个副本,并行度仍然会很低,并且会大大阻碍加速。线程安全代码本身不是并行的,你是对的,但可以并行运行。拥有多个进程是隔离非线程安全代码的一种有趣且廉价的方法(前提是您有足够的内存)。
    【解决方案5】:

    第一个问题:需要多快完成工作?

    第二个问题:这项工作是否会被隔离到单个物理盒子中,或者您的计算资源上限是多少。

    第三个问题:需要对每个单独的“作业”执行的工作是否需要阻塞,它是序列化的还是可以分割成并行的工作包?

    也许可以考虑一个分布式模型,通过这种模型,您可以通过设计扩展多个节点,从第一个实例、actors、remoteref 首先推出所有这些废话......尝试保持您的逻辑简单易行 - 如此序列化。不要只考虑一个盒子。

    这里的大多数答案似乎都集中在产生线程池和执行程序以及所有这些东西的复杂性上——这很好,但在开始用大量思考使生活复杂化之前,请确保你首先处理了真正的问题围绕您如何管理同步逻辑。

    如果一个问题可以分解,那就分解它。不要为了这样做而将其过度复杂化 - 它会带来更好的工程代码和更少的不眠之夜。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多