【问题标题】:Why are functional languages considered a boon for multi threaded environments?为什么函数式语言被认为是多线程环境的福音?
【发布时间】:2010-05-25 23:42:11
【问题描述】:

我听到了很多关于函数式语言的信息,以及它们如何很好地扩展,因为函数周围没有状态;因此该函数可以大规模并行化。

但是,这对我来说意义不大,因为几乎所有现实世界的实用程序都需要/有状态来处理。我还发现有趣的是,大多数主要的缩放库,即 MapReduce,通常都是用 C 或 C++ 等命令式语言编写的。

我想听听我所听到的这种炒作来自功能阵营的消息。

【问题讨论】:

  • 我敢肯定,缩放库不是用 C/C++ 编写的,因为它非常适合这个问题……而不是可以编写性能更高的代码。
  • @spender:我同意它有点重复。但是,该问题的所有答案基本上都说“嗯,这是因为在函数式程序中你没有可变状态。”这个问题是在问“你如何协调不变性与现实世界问题的建模,这通常需要至少一些可变状态”(好吧,这个问题中没有明确说明,但我认为这就是它所暗示的方式是措辞;我很可能是错的)。我认为其他问题的任何答案都不能令人满意地回答这部分。
  • 我可能疯了,但我的理解是在函数式语言中做“错误的事情”更难,所以期望如果人们用函数式语言编码,代码会神奇地是线程安全的。我的看法是,想要全球(或其他广泛可用的)状态的人会不惜一切代价获得它,而不管语言如何。即使在 C++ 中以函数式风格进行编码也是完全可能的。问题不在于语言(或者“工具”),而在于程序员(“用户”)。
  • 多核 != 多线程。它们是两种不同的东西。多线程仍然适用于单核处理器。
  • @Cletus:是的,但与程序员的预期没有区别。而且我指的是一般意义上的多个并发执行线程,而不是特定的硬件配置。

标签: multithreading functional-programming


【解决方案1】:

添加一个词很重要:“没有共享状态”。

任何有意义的程序(任何语言)都会改变世界的状态。但是(某些)函数式语言无法同时从多个线程访问相同的资源。 共享状态的缺失使得多线程安全。

【讨论】:

    【解决方案2】:

    Haskell、Scheme 等函数式语言具有所谓的“纯函数”。纯函数是没有副作用的函数。它不会修改程序中的任何其他状态。根据定义,这是线程安全的。

    当然,您可以用命令式语言编写纯函数。您还可以找到 Python、Ruby 甚至 C# 等多范式语言,您可以在其中进行命令式编程、函数式编程或两者兼而有之。

    但 Haskell (etc) 的重点是您不能编写非纯函数。好吧,严格来说这不是真的,但大部分都是真的。

    类似地,许多命令式语言具有不可变对象,原因大致相同。不可变对象是其状态一旦创建就不会改变的对象。同样,根据定义,不可变对象是线程安全的。

    【讨论】:

    • 是的,但你也可以用命令式语言来做到这一点......因此我很困惑。对这样的事情进行静态分析并不难。
    【解决方案3】:

    你在谈论两个不同的事情,却没有意识到这一点。

    是的,大多数现实世界的程序都有状态somewhere,但是如果你想做多线程,那个状态不应该是everywhere,事实上,更少的地方它在,更好。在函数式程序中,默认情况下是没有状态的,您可以在需要的地方准确地引入状态,而不是在其他地方。那些处理状态的部分不会那么容易多线程,但是由于您程序的所有其余部分都没有副作用,因此这些部分的执行顺序无关紧要,它消除了并行化的巨大障碍.

    【讨论】:

    • 所以你是说编程风格比语言本身更重要?
    • @Billy ONeal——这不仅仅是编程风格。 FP 翻转了编程模型。在命令式中,大多数默认情况下都是可变的,您必须付出额外的努力来避免突变。在 FP 中,大多数默认情况下都是不可变的,您必须努力使某些东西可变。这是思维和方法的 180 度转变。
    • @Billy ONeal:在很大程度上,这与编程风格有关,但一种语言确实会影响你的编程风格。函数式语言的好处是它们很好地支持这种编程风格。在函数式语言中很自然的事情在命令式语言中更尴尬,反之亦然。从中衍生出的函数式语言还有一些其他好处,例如当您的程序的某些部分保证不会产生副作用时,STM 非常容易。
    • @Billy:这不是我的理解,也不是我见过的大多数 C++ 代码的工作方式。常量正确性要求您希望保持不变的变量是const,以便将该约束编码到您的程序中。它并不要求您设计程序以避免可变状态。
    【解决方案4】:

    然而,这对我来说毫无意义,因为几乎所有的现实世界 实际的程序需要/有状态来照顾。

    你会感到惊讶!是的,所有程序都需要一些状态(尤其是 I/O),但您通常不需要更多。仅仅因为大多数程序都有大量状态并不意味着它们需要它。

    用函数式语言编程鼓励您使用更少的状态,因此您的程序更容易并行化。

    许多函数式语言是“不纯的”,这意味着它们允许一些状态。 Haskell 没有,但 Haskell 有 monads,基本上可以让你从无到有:你使用无状态构造获得状态。使用 Monad 有点麻烦,这就是为什么 Haskell 会强烈激励您将状态限制在程序中尽可能小的一部分。

    我还发现有趣的是大多数主要的缩放库,即 MapReduce,通常用 C 或 C++ 等命令式语言编写。

    在 C/C++ 中编写并发应用程序是“困难的”。这就是为什么最好在经过严格测试和检查的库中做所有危险的事情。但您仍然可以获得 C/C++ 的灵活性和性能。

    【讨论】:

    • 我在想像 en.wikipedia.org/wiki/Dijkstra's_algorithm 这样的东西。假设我想编写一个大规模并行应用程序来找到将某人从 A 点映射到 B 点的最短方法。Dijkstra 的算法依赖于队列。虽然在理论上您可以并行化算法的某些位,但仍然有一部分状态——一个队列——它是共享的,并且是算法运行所必需的。每种动态规划算法都是如此,而这样的算法对于使某些事情变得高效是必要的。
    • 当然 - 函数式语言不是灵丹妙药。有时答案是使用名义上效率较低但更易于并行化的算法(例如,可以轻松用函数式语言表达的算法),从而让您更快地解决问题。其他时候你必须手动构建并发的东西[在 C/C++/whatever 中]。但在可能的情况下使用函数式语言并在必要时求助于其他选项可能是一个很好的策略。
    【解决方案5】:

    高阶函数。考虑一个简单的归约操作,对数组的元素求和。在命令式语言中,程序员通常会自己编写一个循环并一次执行一个元素的归约。

    但该代码不容易实现多线程。当你编写一个循环时,你假设了一个操作顺序,你必须说明如何从一个元素到下一个元素。您真的只想说“对数组求和”,让编译器、运行时或其他任何东西决定如何处理数组,根据需要在多个内核之间划分任务,并将这些结果组合在一起.因此,与其编写一个在其中嵌入一些加法代码的循环,另一种方法是将表示“加法”的东西传递给一个可以进行除法运算的函数。一旦你这样做,你就在功能上写作。您将一个函数(加法)传递给另一个函数(reducer)。如果你这样写,那么它不仅使代码更具可读性,而且当你改变架构,或者想为异构架构编写时,你不必改变 Summer,只需要改变 reducer。在实践中,您可能有许多不同的算法,它们都共享一个 reducer,所以这是一个很大的回报。

    这只是一个简单的例子。您可能希望以此为基础。将其他函数应用于二维数组的函数、将函数应用于树结构的函数、组合函数以应用函数的函数(例如,如果您有一个层次结构,上面有树,下面有数组)等等。

    【讨论】:

    • 是的,我明白了。但是在命令式语言中也有很多方法可以处理这个问题——即线程池。是的,您正在编写功能,但除了“有人已经为我编写了特定的功能”之外,我看不出在功能语言中有什么不同
    • 不,这与线程池完全正交。我说的不是管理线程的代码,而是接口。启动reduce 操作的方式是将函数作为参数传递给它。那就是函数式编程。 MapReduce 是功能性的。它有两个参数,一个 map 函数和一个 reduce 函数。作用于函数的函数称为高阶函数。这就是函数式编程的全部意义所在。
    猜你喜欢
    • 1970-01-01
    • 2011-04-01
    • 2013-09-22
    • 1970-01-01
    • 2010-09-28
    • 2013-06-27
    • 2011-09-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多