为什么闭包突然有用于优化在多个内核上运行的程序?

时间:2009-10-18 18:58:17

标签: programming-languages concurrency closures multicore

我读了article声称封锁(或“封锁”)是“多核战争”中的有用武器,因为

  

[...]他们允许你创建单位   工作,每个都有自己的副本   堆栈,不要踩到每个   结果是其他的脚趾。更重要的是,   你可以通过这些单位   实际上,它们是价值观   它们包含一大堆价值观   (双关语)和可执行代码   执行一些操作。

现在,我没有讨论一般情况下闭包的有用性可能也用于共享内存模型中的并发编程,但是与一个线程有什么不同呢?仅作用于本地数据(或进程,或演员,或......)?

作为没有调度程序的线程的并发编程,它本身不是一个闭包吗?

具有非局部副作用的闭包是什么?

3 个答案:

答案 0 :(得分:4)

争论的焦点是,在编程语言中使用闭包可以更容易地在另一个线程中完成一些工作。我认为作者应该在该论证中提到高阶函数的重要性。

我最喜欢的高阶函数介绍是"Why functional programming matters",我不会尝试在这里提供一个不好的副本。

因此,如果你要在for循环中执行闭包,那么使用闭包并不能免费提供并行性,例如。

for (int i = 0; i < numElements; i++) {
  result[i] = closure(inputs[i], i);
}

因为语言无法判断closure(a, b)是否以某种方式更改了结果或输入数组中的其他值。但是具有高阶函数的语言(如map)指定传递给map的函数不应查看或更改输入中的其他值,并防止它影响其他结果。因此,类似下面的代码(在函数式语言中很常见)可以为您并行化,而无需创建工作线程池并将闭包交给它们:

results = map(closure, inputs, [0..numElements-1]);

在这些语言中,闭包消除了在短片代码中声明新功能的痛苦。这使得使用高阶函数变得更有趣。

以下Haskell代码定义了一个函数f,它接受​​一个数字列表并返回一个列表,其中每个输入i都替换为2i+1。通过节省创建计算2i+1函数的麻烦,这是1行代码而不是2行。

f nums = map (\i -> 2*i+1) nums

再次,请参阅"Why functional programming matters"以获取有关如何扩展到实际代码库的强有力论据。

答案 1 :(得分:2)

这是一个很好的闭包定义:

  

“闭包”是一种表达方式   (通常是一种功能)可以拥有   自由变量和   绑定这些变量的环境   (“关闭”表达式)。

我认为你的定义令人困惑,例如,在javascript中,我的闭包可能经常会产生非本地副作用,因为我正在更改DOM。

闭包非常有用,这就是C#将它们添加到语言中的原因。

在函数式编程语言等语言中,它们似乎不一定会创建线程,您必须为上下文切换付出代价,但要创建轻量级进程。框架或编译器将控制要创建的内容,以确保最佳地利用处理器。

使用闭包编写是否比使用不可变数据更重要。

例如,如果我有一个没有全局数据的应用程序,但是每个线程都使用它自己的本地副本,则由操作系统和调度程序决定我的应用程序将使用哪些内核。不幸的是,在C / C ++中,编译器不知道如何做得那么好,所以通过转向FP,我们可以使用已经处理分布式处理很长时间的框架,例如Erlang,以及杠杆他们的经历。

像Erlang这样的Actors比C / C ++线程的开销更少,因为演员的切换速度似乎更快。

答案 2 :(得分:2)

该文章的引用将一些误解包含在一个句子片段中:

  

[...]他们允许你创建单位   工作,每个人都有自己的   堆栈的副本,​​不要踩到   因此,彼此的脚趾。

对于带闭包的语言,这通常不正确。

首先,为了提高效率,他们更经常将引用添加到堆栈中,而不是副本。在大多数语言中,您可以通过引用来修改内容。因此,在这些语言中,此功能根本不提供隔离工作单元的方法。如果有的话,它会使它更加混乱。

其次,在大多数(理智)语言中,您可以引用任何词法封闭本地函数。你不能在堆栈中的任何地方引用任何东西。例如,您无法深入调用调用函数的函数的函数的局部变量...等。您只能访问本地声明的函数的变量/参数,其文本包含发生使用的文本。局部变量和参数(“在堆栈上”)也不是唯一可能在词法上包含函数的东西。所以“堆栈”是在这里调用的错误概念。

Java是一种语言的一个示例,它只能在其“匿名内部类”闭包中获取局部变量和参数的副本。但是,对于外部类的this引用,它仍然会引用。

在关闭this的情况下,内部类现在将存储对外部类的隐式引用 - 实际上与堆栈无关。

C#中的情况类似,但局部变量和参数是通过引用捕获的,而不是被复制。

var counter = 0;
Repeat(10, () => counter++);

假设Repeat是一个启动另一个线程的库函数,现在将每10毫秒调用一次传递给它的Action lambda。你可以希望看到这是一种非常简洁的方式来创造竞争条件!

唯一可以避免这个问题的语言是纯函数式语言,比如Haskell,但这不是由于闭包 - 显然 - 但是由于你永远不能修改 any < / em>共享状态。 (并且Haskell仍然不会完全避免这个问题;大多数真正的软件必须在某个时刻与程序外部的共享状态进行交互,而Haskell的标准库有一种方法可以做到这一点。)