功能语言如何处理共享状态数据?

时间:2018-10-22 23:08:21

标签: concurrency functional-programming mutex

我一直在学习函数式编程,并且可以肯定它可以使并行性更易于处理,但是我看不出它如何使共享资源的处理更容易。我已经看到人们谈论变量不变性是一个关键因素,但是这如何帮助两个线程访问同一资源?假设有两个线程将请求添加到队列中。他们俩都获得队列的副本,添加请求后进行新副本(因为队列是不可变的),然后返回新队列。第一个返回请求将被第二个覆盖,因为每个线程获得的队列副本没有其他线程的请求。因此,我假设在功能语言中提供了一种互斥锁的锁定机制?那这与命令性方法有什么不同?还是函数式编程的实际应用仍然需要一些命令性操作来处理共享资源?

1 个答案:

答案 0 :(得分:0)

可以尽快更新您的全局数据。您正在打破纯粹的功能范式。从这个意义上讲,您需要某种命令结构。但是,这非常重要,以至于大多数功能语言都提供了一种方法来执行此操作,因此您需要它能够与世界其他地方进行通信。 (最复杂的形式是Haskell的IO monad。)除了对某些其他同步库的简单绑定以外,如果可能的话,它们可能会尝试实现无锁,无等待的数据结构。

一些方法包括:

  • 只需写入一次且从未更改的数据即可安全地访问,而无需锁或等待大多数CPU。 (通常有一条内存围栏指令,以确保内存以生产者和消费者的正确顺序更新。)
  • 某些数据结构,例如差异列表,具有可以在不使任何现有数据无效的情况下进行更新的属性。假设您有关联列表[(1,'a'), (2,'b'), (3,'c')],并且想通过将第三个条目更改为'g'进行更新。如果将其表示为(3,'g'):originalList,则可以使用新版本更新当前列表,并保持originalList有效且不变。看到它的任何线程仍然可以安全地使用它。
  • 即使您必须解决垃圾收集器的问题,只要在复制原始状态时不会删除原始状态,每个线程都可以创建自己的共享状态的线程本地副本。基本的底层实现将是生产者/消费者模型,该模型可以自动更新指向状态数据的指针,并在更新和复制操作之前插入存储栅栏指令。
  • 如果程序具有原子比较和交换的方法,并且垃圾回收器知道,则每个线程都可以使用receive-copy-update模式。线程感知垃圾回收器将在任何线程使用旧数据的同时保留旧数据,并在使用完最后一个线程时将其回收。这不需要锁定软件(例如,在现代ISA上,增加或减少字长的计数器是一种原子操作,而原子的比较和交换操作无需等待)。
  • 功能语言可以添加扩展名,以调用以其他某种语言编写的IPC库,并就地更新数据。在Haskell中,可以使用IO monad来定义此参数,以确保顺序的内存一致性,但是几乎每种功能语言都可以通过某种方式与系统库交换数据。

因此,功能语言确实提供了一些保证,这些保证对于有效的并发编程很有用。例如,大多数当前的ISA在最多只有一个编写器的情况下,不会在多个阅读器线程上增加额外的开销,某些一致性错误不会发生,并且功能语言非常适合表达这种模式。