当然它们是很不一样的东西。
在上一篇文章中我也表述对这种的喜爱。
Erlang
Erlang大概是2012年接触的。然后就被震撼了:
- 简单的语法
- 随意的spawn erlang process
- 基于消息传递数据
- 模式匹配
- 递归
- 优雅的二进制处理
- 强大的OTP框架
- 分布式支持
- 自带NOSQL
在学习了Erlang后,总是想着用Erlang搞点事情出来,
于是先后有了:
- make-proxy 你懂的
- code-battle 当时Erlang练手的一个个人项目
在不断的学习使用 Erlang, Gevent的过程中,我也有了自己的感受和看法,我就依次讲讲几个印象深刻的地方:
简单的语法
Erlang没有多少语法糖,直接了当,看别人代码很容易。
(特别是基于OTP的项目)
虽然Python基础语法也很简单,并且哲学就是干一件事情只有最好的一个办法。但是Python的各种语法糖会让你看别人代码不是那么行云流水般流畅。
比如Python用了三年多了,前几个月才知道还可以这么写:
with A() as a, B() as b, C() as c:
pass
基于消息传递数据
Pid ! MyData
如此简洁的语法, 可以在一个Erlang Node内,跨Node,跨机器发送消息。 MyData
是任何Erlang term
。 也就是任意Erlang合法数据。
模式匹配 & 递归
-module(fibo).
-export([start/1]).
-vsn(1.1).
start(Num) ->
ResultList = start(Num, 0, []),
Result = lists:sum(ResultList),
io:format("~p~n", [ResultList]),
io:format("~p~n", [Result]),
ok.
start(0, _, Result) ->
[0 | Result];
start(1, _, Result) ->
[1 | Result];
start(Num, 0, Result) ->
start(Num, 1, [0 | Result]);
start(Num, 1, Result) ->
start(Num, 2, [1 | Result]);
start(Num, Acc, [H1, H2 | _Tail] = Result) when Acc =< Num ->
start(Num, Acc + 1, [H1 + H2 | Result]);
start(_, _, Result) ->
lists:reverse(Result).
这个简单的例子就说明了模式匹配和递归是多么的符合人的自然思维。而且在模式匹配期间就可以从列表
和元组
中提取自己所需的数据。
模式匹配的痛苦
Erlang程序内部自己的匹配真的很爽,但是如果遇到需要去 使用
一些 通用数据格式协议 (比如 protobuf
, msgpack
) 要和其他语言交互的时候,那真是遇到恶魔了
比如一个 protobuf 的定义如下:
message MyMessage {
required int32 id = 1;
required string name = 2;
optional int32 age = 3;
}
Python中是这样来打包/解包数据的:
# 打包
msg = MyMessage()
msg.id = 1
msg.name = "hi"
data = msg.SerializeToString()
# 解包
msg = MyMessage()
msg.ParseFromString(data)
print msg.id
print msg.name
print msg.age
Erlang是这样的:
%% 打包
Data = proto_pb:encode_message({
mymessage,
1,
<<"hi">>,
undefined,
}).
%% 解包
{mymessage, Id, Name, Age} = proto_pb:decode_cmd(Data).
好,我们现在在 MyMessage
中加一个域:
message MyMessage {
required int32 id = 1;
required string name = 2;
optional int32 age = 3;
optional int32 gender = 4;
}
Python 程序不用修改, Erlang 必须修改。因为要全部匹配啊。
在与他人合作的时候,如果采用protobuf
, msgpack
,这些数据格式,那么修改消息定义,对Erlang就是个灾难!
虽然我在工作中有着很大的自主权,选择自己喜欢的技术,但是正是由于这个原因我没在大项目中用过Erlang。
当然,还是自己Erlang技术不过硬。
优雅的二进制处理
我就举一个简单的例子。获取以大端序表示数字5的 4bytes 长的binary
Python中这么做:
import struct
fmt = struct.Struct('>i')
binary = fmt.pack(5)
Erlang:
<<5:32>>.
当然你也可以明确的写成
<<5:32/integer-big>>
这不过这是默认行为而已。
是不是觉得太方便了,Python 的 struct那一大堆格式谁TM记得住啊。你心里是不是窃喜 用erlang写socket发包收包处理包头方便多呢? 骚年, Erlang在建立socket如果指定了 {packet, N}
参数,如果N是 1,2,3,4这写数字,那么发数据添加包头,收数据去除包头已经帮你搞定了!
得益与 模式匹配, 你可以这样:
<<A:16, B:16, C/binary>> = <<D>>
%% A 是 D 的第一和第二字节
%% B 是 D 的第三和第四字节
%% C 是 D 的第五到最后剩下的字节
%% 当然D的长度得》=4 字节。否则会报错。
Python 里有 列表解析
, Erlang里也有,并且更强大。
而且 Erlang 还有 二进制列表解析
!
强大的OTP框架
gen_server
, gen_fsm
, gen_event
, supervisor
这些模式组成了OTP的分层架构。它让你写的程序思路清晰,各个组件分工明确。
Gevent
然后在回来看看Gevent。
我先学的Erlang,然后才看的Gevent这个Python的库。
刚开始学Gevent的时候总是要和Erlang去对比,觉得这么库很难理解啊。比如:
我在Python shell中 gevent.spawn一个函数,它怎么不运行? 按照自然的思维就是spawn后就应该要运行啊。Erlang就是这样的
为什么spawn出的协程要join,一调用join就会把整个python process阻塞住。丝毫感觉不到异步啊。
在学习两次失败后,后来终于想明白了:
shell 中spawn不运行,是因为gevent 的event loop没有跑起来,无法去调度greenlet。
join就是在启动gevent 的 gevent loop,一旦gevent loop通过其他方式启动起来了, 那么就可以在程序中自然的spawn进程。新spawn的 greenlet 会被调度执行。
当跨过这个障碍后,学习和使用Gevent容易了很多。
得易于Python这么有好的语言,大量的库,还有Pycharm
这么优秀的IDE。 (我以前是VIM死忠。。。),再加上gevent这么给力的库,写并发真是轻松惬意啊。
然后就有了基于Gevent的项目:
最近对Gevent又有了新的感悟。
所以有了这篇文章,以及用了一天时间将 codebattle 的服务器写了个Gevent的版本 codebattle-server-python
结论
Erlang很好,天生就是为了服务器编程而生。
但从我上面的 模式匹配 的描述,以及Python的易用性,
以后会更多的用Gevent
最后附上一个 激情视频