Fork me on GitHub
随笔 - 215  文章 - 13  trackbacks - 0
<2018年12月>
2526272829301
2345678
9101112131415
16171819202122
23242526272829
303112345


专注即时通讯及网游服务端编程
------------------------------------
Openresty 官方模块
Openresty 标准模块(Opm)
Openresty 三方模块
------------------------------------
本博收藏大部分文章为转载,并在文章开头给出了原文出处,如有再转,敬请保留相关信息,这是大家对原创作者劳动成果的自觉尊重!!如为您带来不便,请于本博下留言,谢谢配合。

常用链接

留言簿(1)

随笔分类

随笔档案

相册

Awesome

Blog

Book

GitHub

Link

搜索

  •  

积分与排名

  • 积分 - 211162
  • 排名 - 118

最新评论

阅读排行榜

本文为csdn I_myours 原创,此处为转载。作者秉持了 free, open, share 的原则,但敬请各位在转载的时候自觉标明原创作者及出处,这是各位对作者劳动成果的自觉尊重!
作者:I_myours
原文:http://blog.csdn.net/wwh578867817/article/details/49774169

首先,我们聊聊现实世界中的并发。

我曾经举过一个并发和并行的例子:

老妈在很短的时间给我安排了很多任务,吃包子,拖地,洗碗…等等 
由于我母亲大人比较严厉,所以我“愉快”地接受了所有任务。 
这就是所谓的并发,在某一时段,能接受的事件 
我只有两只手,可以用一只手来吃包子,另一只手来拖地。 
这就是所谓的并行,在某一时刻,能处理的事件

现实世界中,并发的事情无处不在,就拿上面的例子来说,我答应了老妈的多个要求,老妈也收到了我的回复。这说明我们的大脑天生就是并发的,它可以在某一时段接受大量的信息。

所以,符合我们思维的并发应该如上图,我可以接收老妈的多个消息,即使老爸回来给我发消息,我也能接收。

这是现实世界中的并发,人与人之间是单独的个体,通过发送消息进行交流 
此时,你可能已经猜到这就是Erlang 中的并发,Actor模型的样子。 
让我们先跨过这个,来看看传统的并发是如何做的。


共享内存

在我们平时写的程序中,并发事件处理一般是通过多线程或多进程来处理。 
某一时刻,我们同时接收到了多条请求,通常的做法是将它放入队列(共用的内存)中去,每一个线程或者进程都会去队列中取消息进行处理。 
如下图: 
这里写图片描述

此时,队列中的内存是共享的,多个线程或进程存取会造成竟态条件,也就是产生竞争,发生错误。

通常的做法是 加锁

线程或进程必须先抢到锁,接着抢到锁的才能访问队列中的消息。 
注意,锁是错误的源泉 
我们应该都遇到过死锁等错误,先不说性能,调试起来就很麻烦。

那么 无锁 CAS 呢?

每个线程或进程都先取出一条消息,保存旧值,拷贝一份后修改为新值,将自己保存的旧值和原先队列中的值比较,若相同说明没有被其它线程或进程修改,则这条消息属于该线程或进程,可以处理此消息。 
若旧值和新值不同,说明有其它线程或进程得到了此消息,则循环进行下一次 CAS 操作。 
这就是所谓的copy and set

这里我们不对比这两种方式以及一会说的 Actor 模型性能的好坏。 
因为在不同的场景下,不同的方式性能也是不同的。我们需要根据具体情况,测试,分析,从而得出性能最优的方式。

顺便提一句,Actor只是模型,仅仅表现给我们的是消息传递。至于它内部怎样实现这里不讨论,感兴趣可以看看内部实现

不过从刚才的描述来看,上述方式都不符合我们的思维,而且略复杂,相信没有人是通过 共享大脑 来传递及处理消息的吧?


来看看 Actor 模型

Actor 模型概念非常简单,且非常符合我们的思维。 
万物皆为 Actor,Actor 与 Actor 之间通过发送消息来通信。

就和人类一般,一个人是一个 Actor,人与人之间通过消息来交互。

别惊讶,Actor 模型就是这么简单。(Actor 模型更多细节参见 Wiki Actor)

接着我们来看 Erlang 是如何运用 Actor 模型的。

Erlang 的 Actor 模型也非常简单。 
在 Erlang 中,进程为最小的单位,也就是所谓的 Actor。注意 Erlang 的进程不是我们传统上的进程,它运行在 Erlang 虚拟机上,非常小,非常轻,可以瞬间创建上万,甚至几十万个,进程间完全是独立的,不共享内存。在进程运行时若出现错误,由于进程的轻量级,Erlang 采取的措施是“让其他进程修复”和“任其崩溃”。在 Erlang 上查看默认限制数量是26万多,可以进行修改。每个进程创建后都会有一个独一无二的 Pid,这些进程之间通过 Pid 来互相发送消息,进程的唯一交互方式也是消息传递,消息也许能被对方收到,也许不能,收到后可以处理该消息。如果想知道某个消息是否被进程收到,必须向该进程发送一个消息并等待回复。

Erlang 中的并发编程只需要如下几个简单的函数。

Pid = spawn(Mod,Func, Args) 
创建一个新的并发进程来执行Mod模块中的 Fun(),Args 是参数。

Pid ! Message 
想序号为 Pid 的进程发送消息。消息发送是异步的,发送方不等待而是继续之前的工作。

receive… end 
接受发送给某个进程的消息,匹配后处理。

receive
    Pattern 1 [when Guard1] ->
        Expression1;
    Pattern 2 [when Guard2] ->
        Expression2;
    ...
    after T ->
        ExpressionTimeout
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

某个消息到达后,会先与 Pattern 进行匹配,匹配相同后执行,若未匹配成功消息则会保存起来待以后处理,进程会开始下一轮操作,若等待超时 T,则会执行表达式 ExpressionTimeout。

举个例子: 
现在我们要进行两个进程的消息传递,一个进程发送Num1 和 Num2以及对应的操作标识,另外一个进程接受到消息后计算。 
比如 {plus, Num1, Num2} 就是求 Num1 和 Num2 的和。

-module(calculate).
-export([loop/0, start/0]).

% 创建新进程,并执行 loop 函数。
start() -> spawn(calculate, loop, []).

% loop 函数
loop() ->
    receive                     % 接受消息并进行匹配
        {plus, Num1, Num2} ->   % 匹配加法
            io:format("Num1 plus Num2 result:~p~n", [Num1 + Num2]),
            loop();
        {reduce, Num1, Num2} -> % 匹配减法
            io:format("Num1 reduce Num2 result:~p~n", [Num1 - Num2]),
            loop();
        {multi, Num1, Num2} ->  % 匹配乘法
            io:format("Num1 multi Num2 result:~p~n", [Num1 * Num2]),
            loop();
        {divis, Num1, Num2} ->  % 匹配除法
            io:format("Num1 divis Num2 result:~p~n", [Num1 div Num2]),
            loop()
    after 50000 ->              % 超时操作
              io:format("timeout!!")
    end().
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

执行结果:

这里写图片描述

我们一行一行来看。

第一行(标号为 1>),编译 calculate.erl。

第二行,执行 calculate 模块中的 start 函数并且创建进程,创建后将进程的 Pid 赋值给 Pid1。

第三行,打印 Pid1。

第四行,创建 Pid2 的进程并且向 Pid1 进程发送请求消息,计算 Num1 和 Num2 的和,Pid1 进程计算完毕后并打印。

第五行,计算减法并打印。

第六行,报错因为 Pid3 已经被使用,在 Erlang 中变量赋值一次后就不能被改变了,不会变就不会出现多个进程修改导致不一致问题。

第七行,计算乘法并打印结果。

第八行,计算除法并打印结果。

最后,超时发生错误。

由此可见 Erlang 并发编程也非常简单且符合人们的思维。

这篇文章就简单地介绍到这里,希望有所帮助^_^。

posted on 2016-12-16 12:20 思月行云 阅读(549) 评论(0)  编辑 收藏 引用 所属分类: Erlang

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理