18

我最近读了很多关于函数式语言的文章。由于那些只使用不可变结构,他们声称并发问题得到了极大的改进/解决。我在理解如何在现实生活中实际实现这一点时遇到了一些严重的问题。假设我们有一个 Web 服务器,其中一个线程在端口上侦听(好吧,IO 是另一件事,我很难理解,但现在让我们忽略它);在任何连接尝试中,都会创建一个套接字并将其传递给一个新创建的线程,该线程对其进行一些工作,并且根据接收到的通信,可能会将更改应用于服务器应用程序全局的大列表/数据结构。所以,

我的问题理解是:

  • 显然,任何线程都可以获得列表的不变“快照”来处理。但是,在通过创建应用了更改的新版本列表来“更改”内容之后,我们仍然让每个线程都有自己的列表版本。这些是如何重新合并在一起的?
  • 另一种方法可能包括使用传统的锁定机制,如 mutex/cond 或 go-like-channels。但是,当所有变量都是不可变的时,你怎么会创建这样的东西呢?
  • 我听说过 STM,但是它不能处理副作用(即,如果列表也可以透明地将数据备份到文件或数据库中)

那么你将如何用函数式语言对这样的事情进行建模呢?

4

1 回答 1

20

不可变值有几个非常适合的应用。并发/并行处理只是最近变得更重要的其中之一。以下确实是从经验和许多有关该主题的书籍和谈话中得出的最基本的摘要。您最终可能需要深入研究一些内容。

您在此处展示的主要示例是关于管理全局状态,因此不能纯粹“一成不变”地完成。然而,即使在这里也有很好的理由使用不可变数据结构。我脑海中的一些:

  • try - catch 表现得更好,因为您不会修改可能中途修改的共享对象,具有不可变的值,它会自动保持最后的一致状态
  • 将更改状态减少为仅对非常有限的一组全局变量(理想情况下是一个)进行多核安全“比较和交换”操作,完全消除死锁
  • 在没有任何防御性复制的情况下自由传递数据结构,这通常是在被遗忘时出现神秘错误的情况(很多时候,防御性副本是在调用和被调用函数中创建的,因为开发人员在经过几次调试后开始倾向于“比抱歉更安全”会话)
  • 更容易的单元测试,因为许多在不可变值上运行的函数是无副作用的
  • 通常更容易序列化和更透明的比较语义更容易调试和获取(记录)系统状态的当前快照,甚至异步

回到你的问题。

在最简单的情况下,这种情况下的全局状态通常使用顶部的一个可变引用来建模,该引用持有一个不可变的数据结构。

引用仅由 CAS 原子操作更新。

不可变数据结构由无副作用函数转换,当所有转换完成后,引用将自动交换。

如果两个线程/内核想要同时交换从同一个旧线程获得的新值,那么首先执行的那个会赢得另一个不会成功(CAS 语义)并且需要重复操作(取决于转换,要么更新当前的使用新值,或从头开始转换新值)。这可能看起来很浪费,但这里的假设是重做一些工作通常比永久锁定/同步开销更便宜。

当然,这可以通过划分不可变数据结构的独立部分来优化,以通过独立更新多个引用来进一步减少潜在的冲突。

对数据结构的访问是无锁的,速度非常快,并且总是给出一致的响应。在任何系统中都会出现边缘情况,例如当您发送更新而另一个客户端随后收到较旧的数据时,因为网络请求也可能出现故障......

STM 很少有用,通常您最好使用数据结构的原子交换,其中包含您将在 STM 事务中使用的引用中的所有值。

于 2013-06-10T12:20:02.463 回答