erlang开发经验谈:防坑指南

任何语言在使用中都会遇到这样那样的问题,erlang也是。这里整理下我遇到的一些问题,避免继续踩坑。说实话,“防坑指南”这个标题有点过于标新立异,不过还是希望能引起重视,避免在实际开发中重复犯这些问题。

 

'--' 运算与 '++'运算

1> [1,2,3,4] -- [1] -- [2]. 

[2,3,4]

算是erlang经典的问题了。这是从后面算起的,先算 [1] -- [2] ,得到 [1] 后被 [1,2,3,4] --,最后得到 [2,3,4]

 '++'运算也是一样的,也是从后面开始算起。

2> [1,2,3,4] -- [1] ++ [2,3,4].[]

另外,以下这种情况也要注意,只会减去遇到的第一个元素。

3> [1,2,3,2] -- [2].

[1,3,2]

 

 

erlang:function_exported()

这个接口是用来检查模块函数是否导出,但是,如果模块没加载过,这个函数返回值就是false

3> erlang:function_exported(odbc,start,0).

false

4> odbc:start().

ok

5> erlang:function_exported(odbc,start,0).

true

我之前也讨论过这个问题,解决方案看这里

 

 

erlang:list_to_binary()

如果参数是多层嵌套结构,就会被扁平化掉,使用 binary_to_list 不能转成原来的数据,也就是不可逆的。

6> list_to_binary([1,2,[3,4],5]) .

<<1,2,3,4,5>>

如果想可逆,可以使用 erlang:term_to_binary

7> binary_to_term(term_to_binary([1,2,[3,4],5])).

[1,2,[3,4],5]

 

erlang:list_to_atom()

这个函数用于字符串转原子,本身没什么问题,但是erlang系统有原子总数限制,除非整个节点关闭了,否则原子就算没用到了也不会被gc。而 lists_to_atom()  如果要转换的原子不存在,将会自动重新生成。可以换成另外一个函数 list_to_existing_atom ,只会生成已经存在的原子

2> list_to_atom("erlang").erlang3> list_to_existing_atom("erlang").erlang4> list_to_existing_atom("hello").** exception error: bad argument     in function  list_to_existing_atom/1        called as list_to_existing_atom("hello")

也不是说 lists_to_atom 不能使用,避免直接将远程传过来的字符串转成原子。

除了这里提到的,还有 binary_to_atom() , binary_to_term() 也有这个问题。对于binary_to_atom()  可以换成 binary_to_existing_atom() , 但 binary_to_term () 有点不同,参考 erlang binary数据转换的问题 解决这个问题

random:uniform()

这个用于生成随机数,返回一个随机数浮点数。但是,这个函数的随机初始种子是个定值,而且种子就放在进程字典,就是说每个进程生成的随机数都是一样的。坑爹啊。

10> spawn(fun() -> io:format("~w ~w~n",[random:uniform(),random:uniform()]) end)
,ok.
0.4435846174457203 0.7230402056221108
ok
11> spawn(fun() -> io:format("~w ~w~n",[random:uniform(),random:uniform()]) end)
,ok.
0.4435846174457203 0.7230402056221108
ok

所以,解决的方法就是进程启动后要重置随机数种子,然后再使用这个函数。

13> random:seed(erlang:now()), random:uniform().
0.4691405130019146

或者重写掉这个函数:

uniform() ->  
    {A1, A2, A3} = case get(random_seed) of  
               undefined -> erlang:now();  
               Tuple -> Tuple  
           end,  
    B1 = (A1*171) rem 30269,  
    B2 = (A2*172) rem 30307,  
    B3 = (A3*170) rem 30323,  
    put(random_seed, {B1,B2,B3}),  
    R = A1/30269 + A2/30307 + A3/30323,  
    R - trunc(R).

try... catch

try1(ErrType) ->

    try

        case ErrType of

            error -> erlang:error(error);

            exit  -> erlang:exit (exit);

            throw -> erlang:throw(throw)

        end

    catch

        Err -> Err

    end.

8> test:try1(error).

** exception error: error

     in function  test:try1/1 (test.erl, line 7)

9> test:try1(exit).

** exception exit: exit

     in function  test:try1/1 (test.erl, line 8)

10> test:try1(throw).

throw

以上结果只有 throw可以匹配,如果想要全部匹配到要改写这个函数:

try1(ErrType) ->

    try

        case ErrType of

            error -> erlang:error(error);

            exit  -> erlang:exit (exit);

            throw -> erlang:throw(throw)

        end

    catch

        _:Err -> Err

    end.

 

 

匿名函数(fun)

匿名函数的使用场景很多,经常被用作回调函数,比如lists模块就大量使用匿名函数做回调。在现在的erlang版本中,匿名函数会在编译期优化成本地函数。如果把匿名函数以进程字典保存,或者收到其他进程发来的匿名函数消息但是没有处理,就可能导致进程被kill。这是因为模块热更新时会检查旧代码是否被使用,如果有进程使用,就会杀掉进程。如何保存匿名函数,而且不怕热更新时进程被杀掉?有两种做法:1. 以MFA方式,然后这样调用 M:F(A) 或者 apply(M, F, A)2. 以fun M:F/A 形式, 如 fun m:test/0

 

断言(guard)

check(A) when length(A) > 9 ->

 error;

check(_) ->

 ok.

检查list长度,但如果传入的数据不是list,结果又会怎样?

11> test:check(hack).

ok

在 guard 模式下,原本会抛出异常的代码就不会报错了,而是结束当前计算并返回false。

顺道说下以下几种情况:

check1(A) when length(A) > 9 ; true->

 error;

check1(_) ->

 ok.

 

check2(A) when length(A) > 9 orelse true->

 error;

check2(_) ->

 ok.

 

check3(A) when length(A) > 9 , true->

 error;

check3(_) ->

 ok.

 

check4(A) when length(A) > 9 andalso true->

 error;

check4(_) ->

 ok.

12> test:check1(hack).

error

13> test:check2(hack).

ok

14> test:check3(hack).

ok

15> test:check4(hack).

ok

如此可见,orelse 和 ',' 运算还是有区别的。

 

 

io_lib:char_list()

 这个函数在R15和R16下运行结果可能是相反的。

R15下:

Eshell V5.9.3.1 (abort with ^G)

1> io_lib:char_list([10100,10600,20100]).

false

 

R16下:

Eshell V5.10.1 (abort with ^G) 

1> io_lib:char_list([10100,10600,20100]). 

true

结果是截然相反,可想而知,如果写了这段代码,换个版本就可能运行有问题了。erlang的版本过渡还是有点粗放。

 

 

gen_server:cast()

gen_server在跨节点下发消息使用 noconnect ,这是正确的选择,可以参考这篇文章

但是,gen_server:cast 使用中可能会有问题。以下是 cast 的实现代码:

do_cast(Dest, Request) -> 

    do_send(Dest, cast_msg(Request)),

    ok.

    

do_send(Dest, Msg) ->

    case catch erlang:send(Dest, Msg, [noconnect]) of

    noconnect ->

        spawn(erlang, send, [Dest,Msg]);

    Other ->

        Other

    end.

当连接不成功时,cast还会新起一个进程去发消息。这就算了,而且不带 noconnect,就是说还会尽最大可能把消息发出去。本来这里是没有不好,但是一般使用gen_server不会意识到有这个问题。在网络不理想的情况,就会莫名其妙多了很多进程,进程本身有最大数量限制。另外,消息发不出去,还是堆积在内存中。

 

 

不同类型数据比较

不同类型的数据也可以比较大小

16> hack >1.

true

17> [] > 1.

true

18> [] > {a,b,c}.

true

比较公式为:number < atom < reference < fun < port < pid < tuple < list < bit string

 

系统限制

这些有点老生常谈了,不过开发中还是要注意。

1. mnesia或dets有2G限制(无法定制)

2. ets表最大数量默认1400(可用 ERL_MAX_ETS_TABLES 定制)

3. 原子最大数量默认 1048576 (可用 +t 定制)

4. 进程最大数量默认 32768   (可用  +P 定制, 范围1024-134217727)

5. 端口/文件句柄最大数量默认 16384  (可用  +Q  定制, 范围1024-134217727)

2015/3/4 补充erlang随机数函数 random:uniform()

2015/3/6 补充字符串转原子函数 list_to_atom()

2015/4/23 补充匿名函数导致进程被kill情况

参考:http://blog.csdn.net/mycwq/article/details/43775285

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值