【问题标题】:Counting in Erlang (how do I increment a variable?)在 Erlang 中计数(如何增加变量?)
【发布时间】:2011-04-18 21:38:38
【问题描述】:

我发现了 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 中增加变量的“最佳实践”是什么?

【问题讨论】:

  • 这个问题没有用例就没有意义。您可以使用lists:foldl 来计算列表中的内容(或filter+length)。如果您计算gen_server 调用,您可以轻松地使用服务器的状态。
  • 我已经错过了 PHP... static $i; $i++;

标签: erlang


【解决方案1】:

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

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

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

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

  • 计数器初始化为零。

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

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

  • 计数器数组的索引是从一开始的。一个大小为 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 只能用于很少或从不更改的值。您可能会在应用程序启动时创建计数器,将引用存储为持久项,然后在应用程序运行时访问它。

【讨论】:

    【解决方案2】:

    考虑一下 Erlang 中 for 循环的这种实现:

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

    F 是一个函数,您可以从中保存值 IMax 的结果。

    【讨论】:

      【解决方案3】:

      递增计数器的标准方法与您的第一个示例相同。通过将变量添加到调用并递增它。我认为您会因为缺少 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
      

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

      要么使用列表包,要么自己积累结果:

      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】:

        不要使用进程字典。

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

        不要使用进程字典。

        如果你错过了,我能否指出你不应该使用进程字典。

        【讨论】:

        • 另外,不要使用进程字典。
        • 然而奇怪的是,进程字典几乎用于 Erlang/OTP 发行版中的每个应用程序。喜欢inets。或orber。或docbuilder。或ic。或megaco。或tv。或cosNotification。或eunit。或reltool。或compiler。或erts。或test_server。或appmon。或ssh。或debugger。或kernel。或gs。或os_mon。或pman。或stdlib。或percept。或xmerl。或asn1。或mnesia。或common_test。或parsetools。或dialyzer。或者......如果社区保持消息灵通,那么相信“无进程字典”模因会更容易。
        • 一般规则是“如果你想知道是否应该使用进程字典,你不应该使用它”和“你会知道什么时候需要它”。公平地说,虽然有有效的流程字典使用,但据我所知,它们中的大多数与“递增变量”无关,而是“存储流程元数据”。
        • 哦,我同意我给了可怕的建议。我什至不会考虑使用过程字典来增加变量。我只是认为“不要使用进程字典”的愚蠢口头禅是愚蠢的,因为你知道,Erlang 的核心发行版及其所有附带的库都广泛使用了它。你的措辞好多了。 “如果你想知道答案是否定的”和“你会知道什么时候需要它”更有意义。
        【解决方案5】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-02-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-03-28
          • 2019-09-03
          • 1970-01-01
          相关资源
          最近更新 更多