FP支持者声称并发很容易,因为它们的范例避免了可变状态。我不明白
我想提出这个一般性问题,因为这个人是功能正常的新手,但多年来一直在关注我的副作用,并出于各种原因(包括更容易(或更具体地讲,“更安全,较少易于出错的“)并发性。当我瞥一眼功能正常的同龄人及其所做的事情时,至少在这方面,草看上去更绿了,香气也更好了。
串行算法
就是说,关于您的特定示例,如果问题本质上是串行的,并且在A完成之前无法执行B,那么从概念上讲,无论如何,您都不能并行运行A和B。您必须找到一种方法来打破顺序依赖性,就像在答案中基于使用旧游戏状态进行平行移动一样,或者使用一种数据结构,该结构允许对其一部分进行独立修改以消除其他答案中提出的顺序依赖性。或类似的东西。但是肯定有很多这样的概念设计问题,因为事情是不可变的,因此您不必如此轻松地对所有事物进行多线程处理。实际上,有些事情将是串行的,直到您找到某种打破订单依赖关系的聪明方法为止,即使可能的话。
并发更轻松
这就是说,有些情况下我们不能并行化涉及可能潜在显著改善的仅仅是因为可能性,它表现的地方副作用的方案很多情况下可能不是线程安全的。在我看来,消除可变状态(或更具体地说,外部副作用)有很多帮助,其中一种情况是,它将“可能或可能不是线程安全的”转变为“绝对线程安全的”。
为了使该语句更具体,请考虑为我提供一个任务,以在C中实现排序功能,该功能接受比较器并使用该功能对元素数组进行排序。它的含义是相当笼统的,但是我将为您提供一个简单的假设,即它将用于如此规模的输入(数以百万计的元素或更多),毫无疑问,始终使用多线程实现将是有益的。您可以使用多线程排序功能吗?
问题是您不能,因为排序函数调用的比较器可能会引起副作用,除非您知道在所有可能的情况下如何实现(或至少记录在案),这在不降低功能的前提下是不可能的。比较器可能会做一些令人作呕的事情,例如以非原子方式修改内部的全局变量。99.9999%的比较器可能不会执行此操作,但仅由于0.00001%的情况可能引起副作用,我们仍然不能对通用函数进行多线程处理。结果,您可能必须同时提供单线程和多线程排序函数,并将责任传递给使用它的程序员,以便根据线程安全性来决定使用哪个函数。人们可能仍会使用单线程版本,却错过了多线程的机会,因为他们可能不确定比较器是否是线程安全的,
只要合理地确定事物的线程安全性,而又不会到处扔锁,就可以拥有大量的脑力。如果我们仅能保证功能不会在现在和将来造成副作用,那么这些锁就会消失。还有一种恐惧:实际的恐惧,因为任何不得不多次调试竞争条件的人都可能对多线程感到犹豫,他们无法确保110%的人认为线程安全并且将保持这种状态。即使对于最偏执的人(我可能至少是临界点),纯函数也提供了一种放心和自信心,我们可以安全地并行调用它。
这是我认为如此有益的主要情况之一,如果您可以硬保证此类函数是线程安全的,而这是纯函数式语言所能提供的。另一个是功能语言首先常常促进创建没有副作用的功能。例如,他们可能会为您提供持久的数据结构,在这种结构中,输入海量数据结构然后输出全新的,而原始数据只有一小部分更改而不触动原始数据的结构相当有效。那些没有这种数据结构的人可能想要直接对其进行修改,并在此过程中失去一些线程安全性。
副作用
就是说,我不同意我功能上的朋友(我认为他们很酷)的一部分:
[...]因为他们的范例避免了可变状态。
并发性并不一定像我所看到的那样永恒不变。该功能可避免引起副作用。如果函数输入要排序的数组,然后将其复制,然后对副本进行突变以对其内容进行排序并输出副本,则即使您传递相同的输入,它仍然与使用某些不可变数组类型的数组一样具有线程安全性来自多个线程的数组。因此,我认为可变类型在创建非常并发友好的代码中仍有地方,可以这么说,尽管不可变类型还有很多其他好处,包括持久性数据结构,我对它们的不可变属性使用的不是很多,消除了为了创建没有副作用的功能而必须深度复制所有内容的开销。
而且经常会有开销,例如通过改组和复制一些其他数据(可能是额外的间接级别)以及在持久性数据结构的某些部分上使用一些GC的形式来使函数免受副作用的影响,但是我看到我的一个伙伴一台32核计算机,我想如果我们可以更自信地并行执行更多操作,那么交换可能值得。