concurrency - 函数式语言如何处理共享状态数据?

标签 concurrency functional-programming mutex

我一直在学习函数式编程,发现它确实可以使并行性更容易处理,但我不明白它如何使处理共享资源变得更容易。我见过人们谈论变量不变性是一个关键因素,但这如何帮助两个线程访问相同的资源呢?假设两个线程正在将请求添加到队列中。他们都获取队列的副本,创建一个添加了请求的新副本(因为队列是不可变的),然后返回新队列。第一个返回的请求将被第二个请求覆盖,因为每个线程获取的队列副本中不存在其他线程的请求。所以我假设函数式语言中存在一种锁定机制,即互斥锁?那么这与解决问题的命令式方法有什么不同呢?或者函数式编程的实际应用仍然需要一些命令式操作来处理共享资源?

最佳答案

一旦您的全局数据可以更新。你正在打破纯函数范式。从这个意义上说,您需要某种命令式结构。然而,这非常重要,大多数函数式语言都提供了一种方法来做到这一点,并且无论如何你都需要它能够与世界其他地方进行通信。 (最复杂的形式是 Haskell 的 IO monad。)除了与其他一些同步库的简单绑定(bind)之外,如果可能的话,他们可能会尝试实现无锁、无等待的数据结构。

一些方法包括:

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

因此,函数式语言确实提供了一些对于高效并发编程有用的保证。例如,当最多只有一个写入者时,大多数当前的 ISA 不会对多个读取器线程施加额外的开销,不会发生某些一致性错误,并且函数式语言非常适合表达这种模式。

关于concurrency - 函数式语言如何处理共享状态数据?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52938871/

相关文章:

java - 创建 FluentIterable 链作为函数,而不是立即应用

R : How evaluate formals (arguments) of function?

functional-programming - 是否有用于函数式编程语言的 native 编译器

c++ - 为了在 C++ 中线程安全,我应该使用互斥锁保护原始类型上的操作吗?

jakarta-ee - 来自不同客户端的 EJB3 有状态并发调用

concurrency - 绕圈发送消息。

Java内存模型-有人能解释一下吗?

c++ - Actor 模型 : Why is Erlang/OTP special? 你能用另一种语言吗?

C - 多个进程写入同一个日志文件

c++ - 多线程环境中单例资源的互斥保护