在Erlang中计数(如何增加变量?)

时间:2010-09-29 15:37:54

标签: erlang

我已经找到了Erlang风格的循环:带有函数的尾递归,这些函数可以获取所有“不变的变量”:

%% does something, 80 bytes at a time
loop(Line, File) -> loop(Line, File, 0).
loop(Line, File, Count) -> 
    do_something(Line, Count),
    case file:read(File, 80) of
        {ok, Line2} -> loop(Line2, File, Count + 1);
        eof -> file:close(File);
        {error, Reason} -> {error, Reason}
    end.

但是,在Erlang中增加计数器的最佳方法是什么?在大多数编程语言中,计算事物的方式是通过递增变量(即count += 1;)。 Erlang的变量没有变化,因此我们必须具有创造性。幸运的是,我们有选择......

我们可以使用函数传递Counter变量,并在每次函数调用时递增它。我们可以使用流程字典来存储计数,并使用getput来增加计数。我们可以使用ETS,即进程的本地数据存储。我们可以使用柜台流程(!!!):

loop(Count) ->                            
    receive                                   
        { incr } -> 
            loop(Count + 1);              
        { report, To } ->                     
            To ! { count, Count },            
            loop(Count)                           
    end.                                      

incr(Counter) ->
    Counter ! { incr }.

get_count(Counter) ->    
    Counter ! { report, self() },
    receive
        { count, Count } -> Count
    end.

我确信还有其他方法,具体取决于范围。什么被认为是在Erlang中增加变量的“最佳实践”?

5 个答案:

答案 0 :(得分:9)

请勿使用流程词典。

您期望的'正常'循环(即for循环或do while)通常在Erlang中的递归函数中实现,因此如果您正在递增“正常”计数器,请执行此操作在函数调用中,就像你出现在顶部。

请勿使用流程词典。

如果您错过了,我可以指出您不应该使用流程词典。

答案 1 :(得分:3)

这完全取决于你使用计数器的是什么。任何像q系统处理的消息数量这样的全局应该使用ets:update_counter。如果它不是全局的,我通常只将它包含在你展示的参数中。

答案 2 :(得分:3)

我认为你已经创造了一个大问题,而你可以更轻松地处理它 在Erlang中考虑for循环的这种实现:

for( Max, Max, F )  -> [ F(Max) ];
for( I, Max, F )    -> [ F(I) | for( I+1, Max, F ) ].

虽然F是要保存的功能,但IMax的结果却是如此。
是不是更容易掌握?

答案 3 :(得分:0)

递增计数器的标准方法与第一个示例相同。通过向调用添加变量并递增它。我认为你因为缺乏for循环和更新值的可能性而感到困惑。

请注意:

repeat(Times) when Times >= 0 -> repeat(0, Times).

repeat(Times, Times) -> done;
repeat(N, Times) ->
  do_a_side_effect,
  repeat(N + 1, Times).

编译(或多或少)与(在伪代码中)相同的东西:

repeat(Times) ->
  while (N < Times) {
    do_a_side_effect
    N++
  }
  return done

如果你想积累结果,也有办法做到这一点。

使用list包或自己累积结果:

loop(File) ->
  {ok, Fd} = file:open(File),
  loop(Fd, 0, []).

loop(Fd, Count, Acc) ->
  case file:read(Fd, 80) of
    {ok, Line} ->
       Result = do_something(Line, Count),
       loop(Fd, Count + 1, [Result | Acc]);
    eof ->
      file:close(File),
      {Count, lists:reverse(Acc)};
    {error, Reason} -> {error, Reason}
  end.

根据你的例子,类似的东西。

编辑:返回Count作为返回值的一部分,因为它似乎很重要。

答案 4 :(得分:0)

从 Erlang/OTP 21.2(2018 年 12 月发布)开始,您可以使用 the counters module。文档总结得很好:

<块引用>

这个模块提供了一组函数来对共享的可变计数器变量进行操作。该实现不使用任何软件级锁定,这使得并发访问非常有效。计数器被组织成具有以下语义的数组:

  • 计数器是 64 位有符号整数。

  • 计数器在上溢和下溢操作时环绕。

  • 计数器初始化为零。

  • 写操作保证原子性。单次写操作看不到中间结果。

  • 可以使用选项 atomicswrite_concurrency 创建两种类型的计数器数组。 atomics 计数器具有良好的全面性能和良好的一致语义,而 write_concurrency 计数器提供了更好的并发写入性能,但代价是一些潜在的读取不一致。请参阅new/2

  • 计数器数组的索引是基于 1 的。一个大小为 N 的计数器数组包含 N 个计数器,索引从 1 到 N。

例如,让我们创建一个计数器,将其增加 7,然后检查其值:

> MyCounterRef = counters:new(1, [atomics]).
{atomics,#Ref<0.3460962489.1601830917.24209>}
> counters:add(MyCounterRef, 1, 7).
ok
> counters:get(MyCounterRef, 1).
7

如果有多个进程需要访问它,那么您将计数器引用存储在哪里?您可以为此使用 persistent_term,这也在 Erlang/OTP 21.2 中添加:

> persistent_term:put(my_counter_ref, MyCounterRef).
ok
> counters:add(persistent_term:get(my_counter_ref), 1, 9).
ok
> counters:get(persistent_term:get(my_counter_ref), 1).
16

请注意,persistent_term 应仅用于很少或永远不会更改的值。您可能会在应用程序启动时创建计数器,将引用存储为持久项,然后在应用程序运行时访问它。