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

发布于 2024-09-25 13:59:57 字数 1273 浏览 3 评论 0原文

我已经弄清楚了 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 中增加变量的“最佳实践”是什么?

I've figured out the Erlang-style loops: tail-recursion with functions that take all the "variables that don't vary":

%% 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.

But, what is the best way to increment a counter in Erlang? In most programming languages, the way you count things is by incrementing a variable (ie. count += 1;). Erlang's variables don't vary, so we have to be creative. Fortunately, we have options...

We can pass a Counter variable with our functions, and increment it with each function call. We can use the process dictionary to store a count, and get and put to increment it. We can use ETS, the local data storage for processes. We can use a counter process (!!!):

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.

I'm sure there are other ways too, depending on the scope. What's considered "best practice" for incrementing a variable in Erlang?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

云雾 2024-10-02 13:59:57

不要使用进程字典。

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

不要使用进程字典。

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

Don't use the process dictionary.

The 'normal' loop that you are expecting (ie a for loop or a do while) is usually implemented in a recursive function in Erlang so if you are incrementing a 'normal' counter do it in the function calls like you show up top.

Don't use the process dictionary.

In case you missed, can I just point out that you should not use the process dictionary.

蘑菇王子 2024-10-02 13:59:57

这完全取决于您使用计数器的目的。任何全局的东西,比如 q 系统处理的消息数量,都应该使用 ets:update_counter。如果它不是全局的,我通常只是将其包含在您所示的参数中。

It all depends on what you are using the counter for. Anything global like the number of messages handled by q system should use ets:update_counter. If it is not global I usually just include it in the parameters like you showed.

乖乖哒 2024-10-02 13:59:57

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

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

F 是一个函数,您可以通过它保存值 IMax 的结果。

Consider this implementation of a for loop in Erlang:

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

F is a function from which you can save results for values I to Max.

山田美奈子 2024-10-02 13:59:57

递增计数器的标准方法如第一个示例所示。通过向调用添加变量并递增它。我认为您会因为缺少 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 也作为返回值的一部分,因为它似乎很重要。

The standard way of incrementing a counter is as in your first example. By adding a variable to the call and incrementing it. I think that you get confused by the lack of for loops and possibility to update values.

Note that:

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

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

compiles to (more or less) the same thing as (in pseudo code):

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

If you want to accumulate the result there are ways to do that as well.

Either use the lists package or accumulate the result yourself:

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.

Or something similar based on your example.

Edit: returned Count as part of the return value as well, since it seemed to be important.

拥有 2024-10-02 13:59:57

从 Erlang/OTP 21.2(2018 年 12 月发布)开始,您可以使用计数器< /代码>模块。该文档总结得很好:

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

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

As of Erlang/OTP 21.2 (released in December 2018), you can use the counters module. The documentation sums it up well:

This module provides a set of functions to do operations towards shared mutable counter variables. The implementation does not utilize any software level locking, which makes it very efficient for concurrent access. The counters are organized into arrays with the following semantics:

  • Counters are 64 bit signed integers.

  • Counters wrap around at overflow and underflow operations.

  • Counters are initialized to zero.

  • Write operations guarantee atomicity. No intermediate results can be seen from a single write operation.

  • Two types of counter arrays can be created with options atomics or write_concurrency. The atomics counters have good allround performance with nice consistent semantics while write_concurrency counters offers even better concurrent write performance at the expense of some potential read inconsistencies. See new/2.

  • Indexes into counter arrays are one-based. A counter array of size N contains N counters with index from 1 to N.

For example, let's create a counter, increment it by 7, and check the value:

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

So where do you store the counter reference, if more than one process needs access to it? You can use persistent_term for that, also added in 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

Note that persistent_term should only be used for values that seldom or never change. You would presumably create the counter when your application start, store the reference as a persistent term, and then access it while the application is running.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文