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

Concurrency 函数式语言如何处理共享状态数据?,concurrency,functional-programming,mutex,Concurrency,Functional Programming,Mutex,我一直在学习函数式编程,看到它确实可以使并行性更容易处理,但我不知道它如何使处理共享资源更容易。我见过有人说变量不变性是一个关键因素,但这如何帮助两个线程访问同一资源呢?假设两个线程正在向队列添加请求。它们都获取队列的副本,在添加请求的情况下创建新副本(因为队列是不可变的),然后返回新队列。第一个返回请求将被第二个请求覆盖,因为每个线程得到的队列副本不存在另一个线程的请求。所以我假设函数式语言中有一种锁定机制,即la mutex?那么,这与解决问题的强制性方法有何不同?或者函数式编程的实际应用是

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

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

一些方法包括:

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