《Erlang/OTP并发编程实战》读书笔记- 第一章

引言

  • Erlang是一门以进程为核心概念的语言。
  • Erlang还是一门函数式编程语言。

Erlang诞生记

        Erlang和C语言一样源自大型电信公司。对于Erlang来说,要解决的问题在于程序员开发超大规模、高并发和强容错的软件,并彻底改善生产效率、减少软件缺陷的数量。Erlang成形于1988年,1998年以开源的形式对外发布。

        Erlang诞生自一个研究项目,该项目旨在寻求一种更好的编程方式,用以开发当时电信业内那些大流量、高并发、猛龙一般永生不死的控制系统。Joe Armstrong于1985年加入这个项目,来到了位于瑞典斯德哥尔摩的爱立信计算机科学实验室

Erlang的诞生

        Joe发现Prolog基于规则的编程风格可以很好地匹配他之前为描述通话控制问题而发明的手写标记法,于是他开始编写一个Prolog的元解释器。通过这种方式,他扩展了Prolog,模拟出进程切换,用以并发运行多个电话呼叫。
        很快,解释器所能识别的表达式形成了一个支持进程和消息传递的小型语言;虽然是用Prolog实现的,但它更简单,而且是函数式的,也没有用上Prolog的合一和回溯特性。没多久,便有人一语双关地提议将之命名为Erlang (一方面指丹麦数学家A. K. Erlang,他因在通信系统统计领域的贡献而为电信工程师们所熟知;一方面它也是Ericsson Language的简写)。
        1988年,一群真正的用户进行了初期实验,开发了一个全新的电信架构,事实证明该语言极大地提升了生产效率,但当时的实现实在是太慢了。于是从1990年起, Joe,Mike Williams和RoberVirding开始实现Erlang的第一个抽象机。这个抽象机名叫JAM,是一个用C写成的堆栈机,比起最初的Prolog实现要快上70倍。
        1993年,第一本Erlang书籍出版,并于1996年再版。直到这时, Erlang才终于可以算得上是一门真正的语言了。

发展

        1995年,随着一个巨型C++项目的分崩离析, Erlang在爱立信内部获得了空前的发展。该项目被推倒重来,这回用的正是Erlang以及“不过60个”程序员;另外还有一个靠谱的语言支持部门—OTP团队—来给他们做后盾。最终的成果便是取得了巨大成功的AXD301系统,整个系统由一百多万行Erlang代码锻造而成。
        与此同时,1998年,乌普萨拉大学的一群学生正将Erlang本地代码编译作为他们的硕士论文课题,并以此成立了高性能Erlang研究组;最终,HiPE(High Performance Erlang)本地代码编译器被整合进标准Erlang/OTP发行版。此外,爱立信的一个内部项目曾试图将Erlang编译为C来提高执行效率,终因生成的代码过大而失败。然而该项目却衍生出了BEAM,一个更快的、基于寄存器的多线程代码抽象机,一举取代了老旧的JAM。

 第一章 Erlang/OTP平台

  • 理解并发和Erlang的进程模型
  • Erlang的容错与分布式支持
  • Erlang运行时系统的重要属性
  • 什么是函数式编程,如何用Erlang进行函数式编程

 OTP:最初是开放电信平台(Open Telecom Platform),更贴切的名字应该是“并发系统平台”。

1.1 基于进程的并发编程

1.1.1 理解并发

有个常用的半正式定义:“并发,用于形容那些无须以特定顺序执行的事物。”

并行:指两个或多个任务在同一时间同时发生。

并发: 无须以特定顺序执行的事物。(可以在一段时间内交替进行,也可以在同一时间同时发生)。

        Erlang的一大优势就是它帮你隐藏了任务实际执行的细节。如果有额外的CPU(或核,或超线程),Erlang会利用它们并行执行更多并发任务。如果没有,Erlang会利用现有的CPU处理能力一点一点地交替执行任务。你不必操心这些细节,Erlang程序能够自动适配不同的硬件——CPU越多它们跑得越快,前提是任务的组织方式允许它们被并发执行。

1.1.2 Erlang的进程模型 

 在Erlang中并发的基本单位是进程

进程的行为模式,互相隔离,确保自身内部的状态的改变不对其他进程造成影响

进程拥有自己的工作内存空间和自己的信箱,其中信箱用来存放外来消息。

由于进程之间互不共享内部状态,它们只能进行复制式通信。一个进程要跟其他进程交换信息,就会发送一条消息。这条消息是发送方所持有数据的一个只读副本。

1.1.3 4种进程通信范式

 介绍四种进程通信手段,分别是持锁共享内存、软件事物性内存、future和消息传递。

1.持锁共享内存
        两个或多个进程可以同时读写一块或多块常规内存区域。有时进程需要在这些内存区域上执行一些具备原子性的操作序列,其他进程在操作完成前不得访问这些区域,这就需要一种令该进程阻止其他进程访问这些区域的方法。解决之道就是锁:一种一次仅允许一个进 
程访问某种资源的构件
        锁的实现需要内存系统的支持,一般由硬件以特殊指令的形式提供支持。使用锁的时候进程之间必须通力合作:所有进程必须先获取锁才能访问共享内存区域,访问结束后还要将锁释放给其他进程使用。使用锁必须万分小心,差之毫厘谬以千里,因此,操作系统或编程语言分别以系统调用或语言构件的形式提供了信号量、监视器和互斥量等以基本锁为基础的高级构件,用以确保锁的请求和释放的正确性。尽管借助这些可以绕开最棘手的问题,但仍然难以克服锁的诸多缺点。随便列举几条:
□ 即便冲突几率很低,锁的开销仍难以忽略;
□ 它们是内存系统中的竞争热点;
□ 出错的进程可能将正处于加锁状态的锁弃之不顾;
□ 当锁出现问题时极难调试。

2.软件事务性内存(STM)
        STM (  Software Transactional Memory,软件事务性内存),目前可以在Haskell编程语言的GHC实现和基于JVM的Clojure语言中看到这种机制。STM将内存当作传统数据库,用事务来决定何时写入什么内容。通常,这种实现以一种乐观方式来规避锁:将一组读写访问视为单个操作,若两个进程同时试图访问共享区域,则各自启动一个事务,最终只有一个事务会成功。另一个进程会得知事务失败,并应该在检查共享区域的新内容后重试。该模型直截了当,谁都不需要等待其他进程释放锁。
        STM的主要缺点在于你必须重试失败的事务(当然,它们可能再三失败)。事务系统本身也会有比较显著的开销,另外在确定哪个进程成功之前,还需要额外的内存来存放你试图写入的数据。理想情况下,系统应该像支持虚拟内存那样对事务性内存提供硬件支持。

3.Future、Promise及同类机制
        另一个更现代的手段是采用所谓的fiiture或promise。这个概念还有另处一些形式;在E和MultiLisp等语言以及Java的一个库中可以找到它的身影。类似的还有Id和Glasgow Haskell中的I-var和M-var、Concurrent Prolog中的并发逻辑变量,以及Oz中的数据流变量。
        其基本思路是,每个fiiture代表一个被外包到其他进程的计算结果,该进程可能跑在别的CPU甚至是别的计算机上。Future可以像其他对象一样被四处传递,但无法在计算完成之前读取结果, 必须等待计算完成。这种方法虽然概念简单、简化了并发系统中的数据传递,但也令程序在远端进程故障和网络故障面前变得脆弱:计算结果尚未就绪而连接又不幸断开时,试图访问promise的值的代码便会无所适从。

4.消息传递
        Erlang进程靠消息传递来通信。这意味着接收进程实际上获取了一份独立的数据副本,发送方感知不到接收方对副本所做的任何操作。向发送方回传信息的唯一途径就是反向发送另一条消息。由此而得出的一个重要结论是,无论收发双方是身处同一台机器上还是被 
网络所隔离,它们都能以相同的方式进行通信
        消息传递一般可分为两类:同步方式和异步方式在同步方式下,消息抵达接收端之前发送方什么事也做不了;在异步方式下,消息一经投递发送方便可立即着手于其他事务。
        当然,收发双方在这一层面的隔离并不是免费的。复制大型数据结构时成本很高,如果发送方还要保留数据副本,势必会造成较高的内存消耗。

1.1.4  用Erlang进程编程

1.2 Erlang的容错架构

         为了有效处理有缺陷的代码和数据,我们需要能够以容错的系统,以防系统在遭遇到突发状况时土崩瓦解。

        Erlang也具备异常处理机制来捕获特定代码段的错误,不过它还有一套独一无二的可以有效处理进程故障的进程链接系统

1.2.1 进程链接

        Erlang进程意外退出时,会产生一个退出信号。所有与濒死进程链接的进程都会收到这个信号。默认情况下,接收方会一并退出并将信号传播给与它链接的其他进程,直到所有直接或间接链接在一起的进程统统退出为止(参见图1-2) 。这种级联行为可以使一组进程像单个应用一样退出,因此系统整体重启时你不必担心是否还有残存下来未能完全关闭的进程。 

         前面我们曾提到过利用进程来清理复杂状态。其基本原理是:每个进程完整封装自己的全部状态,因此进程退出时系统的其余部分不会受损。如同单个进程一样,这一点对相互链接的进程组也同样适用。一个进程崩溃,与之协作的其他进程也一并退出,如此便可干净利落地抹掉之前建立的所有复杂状态,既节省了程序员的时间也减少了错误。

1.2.2 监督与退出信号捕捉

         OTP实现容错的主要途径之一就是改写退出信号默认的传播行为。通过设置trap_exit进程标记,你可以令进程不再服从外来的退出信号,而是将之捕捉。这种情况下,进程接收到信号后,会先将其转为一条格式为{'EXPT',Pid,Reason}的消息,该消息描述了哪个进程出于什么原因而发生故障,然后这条消息会像普通消息一样被丢入信箱,捕捉到信号的进程就能分检并处理这类消息了。

        这类会捕捉信号的进程有时被称为系统进程,它们执行的代码往往有别于普通的工作进程(即通常不捕捉信号的进程)。身为防范退出信号进一步传播的壁垒,系统进程阻断了与之链接的其他进程和外界之间的联系,因而可用于汇报故障乃至重启故障的子系统,正如图1-3所示。我们将这类进程称为监督者

 1.2.3 进程的分层容错

        通过分层可以将相关的子系统归于同一个监管者的管辖之内。更重要的是,这样做可以定义多个层级的基准工作状态,随时可可供重置。

1.3 分布式Erlang

         Erlang规避了数据共享并通过复制进行通信,这使得Erlang代码可以直接分布到多台机器上。

1.4 Erlang运行时系统和虚拟机

Erlang运行时系统(ERTS),Erlang中所有需要底层支持的东西都由ERTS处理。

ERTS中特别重要的一个部分就是Erlang虚拟机模拟器:是执行Erlang程序经编译后产出的字节码的地方。这个虚拟机就是Bogdan Erlang抽象机(BEAM)。

促成Erlang的强大和高效的3个重要方面:

  • 调度器——处理运行中的Erlang进程,令所有就绪的进程共享可用的CPU资源,并在新消息到达或发生超时的时候唤醒相应的睡眠中的进程;
  •  I/O模型——防止系统在进程与外部设备通信时阻塞,令系统平稳运行;
  • 垃圾回收器——回收不再使用的内存。

1.4.1 调度器 

        ERTS的进程调度器最初的设计目标是在单CPU上并发运行轻量级Erlang进程,而不用关心底层用的是什么操作系统。ERTS运行的 时候通常就是单个操作系统进程(在操作系统的进程列表中一般名为beam或werl )。这个进程中, 就跑着管理所有Erlang进程的调度器。
        随着线程在大多数操作系统中的普及,ERTS也有所变化,开始将I/O系统这类东西从运行 Erlang进程的线程中拿出来,放到独立的线程中去,但完成主体工作的线程仍然只有一个。如果你用的是多核系统,就必须在同一台机器上运行多个ERTS实例。不过从2006年5月起,Erlang/OTP 第11版中增加了对称多处理器(SMP)支持。这是一项重大突破,令Erlang运行时系统可以在内 部使用不止一个进程调度器,每个占用一个独立的操作系统线程。
        这意味着现在Erlang进程可以以n:m的方式映射到操作系统线程。每个调度器处理一个进程池。可并行运行的Erlang进程最多能有m个(每个调度器线程执行一个),但同一池内的进程仍像之前所有进程共用一个调度器那样分时运行。在此基础上,进程可以在进程池之间迁移以便维持可用调度器上的负载均衡。   

补:运行时系统中的所有进程之间相互隔离,单个进程的内存不与其他进程共享,也不会被其他濒死或跑疯的进程破坏. 

1.4.2 I/O与调度 

         Erlang以事件驱动的方式处理所有I/O,当数据进出系统时,程序可以以非阻塞方式完成数据处理。

1.4.3 进程隔离与垃圾回收器

        Erlang进程间的隔离:每个进程所使用的内存都是属于自己的,随着进程的创建和结束而分配和释放。

        垃圾回收(GC)器会定期搜寻和回收不再使用的内存。Erlang进程内使用的是分代复制式垃圾回收器。

关于Erlang进程内存和垃圾回收的扩展内容:Erlang 垃圾回收机制(GC)_YOONGI-CSDN博客

1.5 函数式编程

         函数式编程的主要思想是:将函数看作和整数、字符串一样的数据; 运用函数调用而非while和for这样的循环结构来表达算式;以及不修改变量和值

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值