Multithreading 函数式编程中的不可变数据问题
我不熟悉函数式编程。我所理解的是函数式编程就是使用纯函数编写代码,而不改变数据的值 当我们需要更新变量时,我们在函数式编程中创建新变量,而不是改变变量的值Multithreading 函数式编程中的不可变数据问题,multithreading,haskell,clojure,functional-programming,erlang,Multithreading,Haskell,Clojure,Functional Programming,Erlang,我不熟悉函数式编程。我所理解的是函数式编程就是使用纯函数编写代码,而不改变数据的值 当我们需要更新变量时,我们在函数式编程中创建新变量,而不是改变变量的值 假设我们有一个变量x,它表示程序发出的HTTP请求总数。如果我们有两个线程,那么每当任何线程发出HTTP请求时,我希望线程递增x。如果两个线程都创建变量x的不同副本,那么它们如何同步x的值。例如:如果线程1发出10个HTTP请求,线程2发出11个HTTP请求,那么它们将分别打印10和11,但我如何打印21。我可以为clojure案例提供答案。
假设我们有一个变量
x
,它表示程序发出的HTTP请求总数。如果我们有两个线程,那么每当任何线程发出HTTP请求时,我希望线程递增x
。如果两个线程都创建变量x
的不同副本,那么它们如何同步x
的值。例如:如果线程1发出10个HTTP请求,线程2发出11个HTTP请求,那么它们将分别打印10和11,但我如何打印21。我可以为clojure
案例提供答案。在clojure
中,如果您需要协调对共享状态的访问,则该语言中有一些结构是为处理这些情况而设计的
在这种情况下,可以使用atom
来保存该值。对atom
所做的更改是原子性的,并且将通过clojure的方式乐观地进行更改。原子是clojure的引用类型之一。原子本质上是一个值的引用,它可以通过原子的变异函数以可控的方式随时间变化
有关原子和其他引用类型的更多信息,请参见clojure。我将介绍Haskell部分
MVar
是线程的通信机制之一。这是西蒙·马洛(Simon Marlow)书中的一个例子(该程序不言自明):
在上面的示例中,您可以看到变量
m
中的MVar
值是如何在线程之间共享的。您可以在中了解有关这些技术的更多信息。我还将介绍Haskell部分
首先,我想澄清一下:
我们在中创建新变量,而不是更改变量的值
函数编程时,我们需要更新一个变量
那不太准确。当我们需要时,我们在FP中创建新的“变量”,而不是当我们需要改变现有变量时。当我们做你描述的事情时,我们甚至不考虑突变;我们可能只是认为我们正在创造一种与我们所拥有的价值相似的新价值
你用线程描述的有点不同。实际上,您正在寻找副作用(增加计数器)。哈斯凯尔,作为一个纯粹的人,不会允许你在没有明确说明的情况下产生副作用。因此,在这种情况下,您需要求助于引用类型/可变单元格。最简单的一个叫做IORef
,从这个意义上讲,它非常像一个变量;可以指定值、读取当前值等
所以,正如你所看到的,当你在寻找这些东西时,你真的只有一份计数器
以上是我回答的要点,但您已经具体询问了线程,因此我也将对此作出回应。IORef
s实际上不是线程安全的。因此,有建议的MVar
s。它们不像常规变量,但它们很接近,而且它们能优雅地完成工作。笼统地说:它们抽象变量和锁定。不过,我想你可能会发现TVar
s更容易。它们的行为类似于IORef
/变量,只是与两者不同,它们是线程安全的;您可以将它们的操作组合成一个操作,对它们执行的任何操作都是以原子方式(STM)完成的
顺便说一句,你可能会找到完全避免这种状态的方法,这是非常值得鼓励的。例如,您可以让两个线程执行一个异步递归函数,该函数通过一个参数记住发出了多少请求,然后将其作为返回值。total requests count是所有线程返回的请求总数。这可以避免对计数器的副作用,但它只能在线程完成时产生结果。这是非常有限的,所以有时候你们可能想要那个副作用。好吧,我将尝试在保持状态时提供更一般的解释,因为我认为这是你们真正想要知道的 通常,您可以通过递归完成相同的事情,例如,如果您有以下函数:
somefun ()->
somefun(0).
somefun (X) ->
perform_http_request(),
if(something!=quit)
somefun(X+1)
end function.
generate_thread(0, Accumulator) ->
Accumulator;
generate_thread(X, Accumulator) ->
Y = somefun(),
NewAccumulator = add_to_accumulator(Y),
generate_thread(X-1, NewAccumulator).
我只是匆忙地输入了这段代码,这是一个非常一般的解释(你不能直接使用这段代码),但是你会发现你并不真正了解这里的易变性。。。该函数将在所有线程完成其处理后完成,现在实际的线程同步取决于您选择的语言,不同的语言有不同的并发和“线程”处理方式。。如果您对并发性感兴趣,我建议您看看Erlang,因为它有一个非常好的并发模型
总之,最后您可以将累加器中返回的所有值相加并显示出来,顺便看看foldl和foldr函数 我自己不是大师,但我想你可能没有理解 如果不保持状态,就无法创建非常有用的程序。需要以这样或那样的方式陈述。FP的目标不是避免状态,而是控制状态的使用 这样看,您的状态应该与数据库条目一样隔离和安全如果你像对待数据库一样对待国家,我想你会没事的 这意味着,
- 您将不会有类似
的登录。您宁愿使用函数(inc count)
递增计数代码>将安全地更新计数。注意
代码>,这意味着副作用李>
- 您将没有依赖于副作用的代码。相反,您将依赖于函数
'x' 'y'
somefun ()-> somefun(0). somefun (X) -> perform_http_request(), if(something!=quit) somefun(X+1) end function. generate_thread(0, Accumulator) -> Accumulator; generate_thread(X, Accumulator) -> Y = somefun(), NewAccumulator = add_to_accumulator(Y), generate_thread(X-1, NewAccumulator).
- 您将没有依赖于副作用的代码。相反,您将依赖于函数