函数语言中的并行性

时间:2012-10-08 20:19:32

标签: multithreading parallel-processing functional-programming lang

宣传的FP功能之一是程序“默认并行”,自然适合现代多核处理器。实际上,减少树的性质是平行的。但是,我不明白它如何映射到多线程。考虑以下片段(伪代码):

let y = read-and-parse-a-number-from-console
let x = get-integer-from-web-service-call
let r = 5 * x - y * 4
write-r-to-file

翻译器如何确定应该在线程上运行哪些树分支?在获得xy之后,在单独的线程上减少5 * xy * 4表达式将是愚蠢的(即使我们从线程池中获取它),willn'是吗?那么不同的函数式语言如何处理呢?

2 个答案:

答案 0 :(得分:12)

我们还没到那儿。

纯声明式样式的程序(功能样式包含在此类别中,但其他一些样式也是如此)往往更适合并行化,因为所有数据依赖都是显式的。这使得程序员很容易手动使用语言提供的原语来指定两个独立的计算应该并行完成,而不管它们是否共享对任何数据的访问;如果一切都是不可变的并且没有副作用,那么改变事情的顺序不会影响结果。

如果纯度是强制的语言(如在Haskell,Mercury等中,但不像Scala,F#等,其中纯粹被鼓励但是未被强制执行),那么可能< / em>让编译器尝试自动并行化程序,但默认情况下,我所知道的现有语言都没有。如果该语言允许未经检查的不纯操作,那么编译器通常不可能进行必要的分析以证明给定的自动并行化程序的尝试是有效的。所以我不希望任何这样的语言能够非常有效地支持自动并行化。

请注意,您编写的伪程序可能是不是纯声明性代码。 let y = read-and-parse-a-number-from-consolelet x = get-integer-from-web-service-call正在从不纯的外部操作中计算xy,并且程序中没有任何内容可以修复它们应该运行的顺序。通常,以任一顺序执行两个不纯操作都会产生不同的结果,并且在不同的线程中运行这两个操作会放弃对它们运行顺序的控制。因此,如果像这样的语言是自动并行化你的程序,它几乎肯定会引入可怕的并发错误,或者拒绝显着地并行化任何东西。

然而,功能样式仍然可以轻松地手动并行化这些程序。一个人类程序员可以说,从控制台和网络读取的顺序几乎肯定无关紧要。知道没有共享的可变状态可以决定并行运行这两个操作而不需要深入研究它们的实现(在必须进行的算法中你必须要做的事情是,可能存在可变的共享状态,即使它看起来不像是界面)。

但是,强制纯度语言的编译器自动并行化的一个重要复杂因素是知道如何进行很多并行化。当你尝试在少量处理器上运行大量非常短暂的线程时,并行运行每一个计算都会大大超过产生新线程(更不用说上下文切换)的所有启动成本的任何可能的好处。编译器需要识别数量相当少的“计算块”,并在顺序运行每个块的子计算时并行运行块。

但只有“令人尴尬的并行”程序很好地分解成非常大的完全独立的计算。大多数程序更加相互依赖。因此,除非您只希望能够自动并行化非常容易手动并行化的程序,否则您的自动并行化可能需要能够并行识别和运行“块”,这些“块”部分依赖于彼此,让它们等待当他们得到真正需要一个应该由另一个“块”计算的结果的点。这引入了线程之间同步的额外开销,因此选择并行运行的逻辑需要更好,以便击败仅按顺序运行所有内容的简单策略。

Mercury(纯逻辑编程语言)的开发人员正致力于解决这些问题的各种方法,从静态分析到使用分析数据。如果您有兴趣,他们的research papers会提供更多信息。我认为其他研究正在用其他语言研究这个领域,但我对其他任何项目都不太了解。

答案 1 :(得分:2)

特定的示例中,第三个语句取决于第一个和第二个,但第一个和第二个之间没有相互依赖关系。因此,运行时环境可以在read-and-parse-a-number-from-console的不同线程上执行get-integer-from-web-service-call,但是第三个语句的执行必须等到前两个完成。

在获得y * 4的实际值之前,某些语言或运行时环境可能能够计算部分结果(例如x)。作为一名高级程序员,你不太可能能够发现这一点。