查看源代码 进程

进程

Erlang 是为大规模并发而设计的。Erlang 进程是轻量级的(动态增长和收缩),内存占用小,创建和终止速度快,并且调度开销低。

进程创建

通过调用 spawn() 来创建进程。

spawn(Module, Name, Args) -> pid()
  Module = Name = atom()
  Args = [Arg1,...,ArgN]
    ArgI = term()

spawn() 创建一个新的进程并返回 pid。

新进程开始在 Module:Name(Arg1,...,ArgN) 中执行,其中参数是(可能为空的)Args 参数列表的元素。

存在许多不同的 spawn 内建函数(BIF):

已注册的进程

除了使用 pid 来寻址进程之外,还有用于在名称下注册进程的 BIF。该名称必须是一个原子,并且如果进程终止,则会自动注销。

BIF描述
register(Name, Pid)将名称 Name(一个原子)与进程 Pid 关联。
registered/0返回已使用 register/2 注册的名称列表。
whereis(Name)返回在 Name 下注册的 pid,如果该名称未注册,则返回 undefined

表:名称注册 BIF

进程别名

当向进程发送消息时,接收进程可以通过 Pid注册名称进程别名来识别,进程别名是类型为 reference 的项。进程别名设计的典型用例是请求/回复场景。在发送回复时使用进程别名可以使回复的接收者在操作超时或进程之间的连接丢失时,防止回复到达其消息队列。

当使用 发送运算符 (!) 或发送 BIF(例如 erlang:send/2)发送消息时,进程别名可以用作接收者的标识符。只要进程别名处于活动状态,消息的传递方式就与使用创建该别名的进程的进程标识符相同。当别名被停用时,使用该别名发送的消息将在进入接收者的消息队列之前被丢弃。请注意,在停用时已进入消息队列的消息将不会被删除。

可以通过调用 alias/0,1 BIF 之一或同时创建别名和监视器来创建进程别名。如果别名与监视器一起创建,则相同的引用将同时用作监视器引用和别名。同时创建监视器和别名是通过将 {alias, _} 选项传递给 monitor/3 BIF 来完成的。当通过 spawn_opt()spawn_request() 创建监视器时,也可以传递 {alias, _} 选项。

进程别名可以通过创建该别名的进程调用 unalias/1 BIF 来停用。也可以在某些事件发生时自动停用别名。有关自动停用别名的更多信息,请参阅 alias/1 BIF 和 monitor/3 BIF 的 {alias, _} 选项的文档。

不能

  • 创建标识调用者以外的另一个进程的别名。
  • 停用除非它标识调用者的别名。
  • 查找别名。
  • 查找由别名标识的进程。
  • 检查别名是否处于活动状态。
  • 检查引用是否为别名。

这些都是与性能、可伸缩性和分布式透明性相关的有意设计决策。

进程终止

当进程终止时,它总是以退出原因终止。该原因可以是任何项。

如果退出原因是原子 normal,则称进程正常终止。没有更多代码要执行的进程会正常终止。

当发生运行时错误时,进程以退出原因 {Reason,Stack} 终止。请参阅 退出原因

进程可以通过调用以下 BIF 之一来终止自身:

然后,进程以 exit/1 的原因 Reason 或其他的原因 {Reason,Stack} 终止。

如果进程收到带有 normal 以外的退出原因的退出信号,也可以终止,请参阅 错误处理

信号

Erlang 进程和 Erlang 端口之间的所有通信都是通过发送和接收异步信号来完成的。最常见的信号是 Erlang 消息信号。可以使用 发送运算符 ! 发送消息信号。接收进程可以使用 receive 表达式从消息队列中获取接收到的消息。

同步通信可以分解为多个异步信号。这种同步通信的一个例子是,当第一个参数不等于调用进程的进程标识符时,调用 erlang:process_info/2 BIF。调用者发送一个异步信号请求信息,然后阻塞等待包含请求信息的回复信号。当请求信号到达其目的地时,目标进程会回复所请求的信息。

发送信号

进程和端口使用许多信号进行通信。下面的列表包含最重要的信号。在所有请求/回复信号对的情况下,请求信号由调用特定 BIF 的进程发送,并且在执行所请求的操作后,回复信号会发送回它。

前面提到的时钟服务、名称服务、定时器服务和派生服务都是运行时系统提供的服务。 每个服务都由多个独立执行的实体组成。 可以将这种服务视为一组进程,实际上也可以这样实现。 由于每个服务都由多个独立执行的实体组成,因此从一个服务发送到一个进程的多个信号之间的顺序是保留的。 请注意,这违反语言的信号排序保证

前面描述的信号的实现可能在运行时发生变化,也可能由于实现中的变化而发生变化。您可以使用receive跟踪或检查消息队列来检测此类更改。但是,这些都是运行时系统的内部实现细节,您应该依赖这些细节。例如,许多回复信号都是普通的消息信号。当操作是同步的时,回复信号不必是消息信号。当前的实现利用了这一点,并且根据系统的状态,使用其他方式传递回复信号。这些回复信号的实现也可能随时更改为不使用之前使用的消息信号。

接收信号

信号是异步和自动接收的。进程不需要执行任何操作来处理信号的接收,或者可以执行任何操作来阻止信号的接收。特别地,信号接收receive表达式的执行相关联,而是可以在进程的执行流程中的任何位置发生。

当进程接收到信号时,会采取某种操作。采取的具体操作取决于信号类型、信号的内容和接收进程的状态。针对最常见信号采取的操作

  • message - 如果消息信号是使用不再活动的进程别名发送的,则消息信号将被丢弃;否则,如果别名仍然处于活动状态或消息信号是通过其他方式发送的,则该消息将添加到消息队列的末尾。当消息添加到消息队列后,接收进程可以使用receive表达式从消息队列中获取消息。

  • link, unlink - 可以将其非常简化地视为更新有关链接的进程本地信息。有关链接协议的详细描述,可以在ERTS 用户指南分布协议章节中找到。

  • exit - 将接收者设置为退出状态,丢弃信号,或将信号转换为消息并将其添加到消息队列的末尾。如果接收者被设置为退出状态,则不会再执行任何 Erlang 代码,并且该进程会被安排终止。下面的接收退出信号部分提供了有关接收到exit信号时采取的操作的更多详细信息。

  • monitor, demonitor - 更新有关监视器的进程本地信息。

  • down, change - 如果相应的监视器仍然处于活动状态,则转换为消息;否则,丢弃信号。如果信号被转换为消息,它也会被添加到消息队列的末尾。

  • group_leader - 更改进程的组长。

  • spawn_reply - 根据回复以及spawn_request信号的配置,转换为消息或丢弃信号。如果信号被转换为消息,它也会被添加到消息队列的末尾。有关更多信息,请参见spawn_request() BIF 函数。

  • alive_request - 安排执行是否活动测试。如果进程处于退出状态,则在释放进程使用的所有直接可见的 Erlang 资源后,才会执行是否活动测试。执行完是否活动测试后,将发送alive_reply

  • process_info_request, garbage_collect_request, check_process_code_request - 安排执行请求的操作。当操作执行完毕后,将发送回复信号。

请注意,接收到信号时采取的一些操作涉及安排进一步的操作,这些操作将在这些安排的操作完成后导致回复信号。这意味着回复信号的发送顺序可能与触发这些操作的传入信号的顺序不同。但是,这违反语言的信号排序保证

进程的消息队列中的消息顺序反映了自所有将消息添加到消息队列的信号都将其添加到消息队列的末尾以来,接收到与消息对应的信号的顺序。来自同一发送者的信号对应的消息也按照与信号发送顺序相同的顺序排序,这是由于该语言的信号排序保证

直接可见的 Erlang 资源

如前所述,由于链接导致的exit信号、down信号以及由于alive_request而来自退出进程的回复信号,只有在终止进程持有的所有直接可见的 Erlang 资源都被释放后才会发送。这里所说的直接可见的 Erlang 资源是指该语言提供的所有资源,不包括堆数据、脏原生代码执行和终止进程的进程标识符所持有的资源。直接可见的 Erlang 资源的示例包括注册名称ETS 表。

排除的资源

在有关进程的所有内容都已释放之前,无法释放进程的进程标识符以供重用。

当进程在接收到退出信号时正在 NIF 中执行脏原生代码时,即使它仍在执行脏原生代码,它也将被设置为退出状态。 直接可见的 Erlang 资源将被释放,但运行时系统无法强制原生代码停止执行。运行时系统会尝试防止脏原生代码的执行影响其他进程,例如,禁用从终止的进程中使用enif_send()等功能,但如果 NIF 行为不端,它仍然会影响其他进程。行为良好的脏 NIF 应该测试它正在执行的进程是否已退出,如果已退出,则停止执行。

在一般情况下,在需要发送的所有信号都已发送之前,无法删除进程的堆。堆数据所持有的资源是包含堆的内存块,但也包括从堆引用的内容,例如堆外二进制文件,以及通过堆上的 NIF 资源对象持有的资源。

信号的传递

信号从发送到到达目的地之间所经过的时间是不确定的,但为正数。如果接收者已终止,则信号不会到达,但它会触发另一个信号。例如,发送到不存在进程的link信号会触发exit信号,该信号会发送回link信号的来源。当通过分布进行通信时,如果分布通道发生故障,信号可能会丢失。

给出的唯一信号排序保证如下:如果一个实体向同一个目标实体发送多个信号,则顺序将被保留;也就是说,如果AB 发送信号 S1,稍后又向 B 发送信号 S2,则保证 S1 不会在 S2 之后到达。请注意,S1 可能已经丢失,也可能没有丢失。

不规则性

  • 同步错误检查 - 当在节点本地发送时,某些发送信号的功能具有同步错误检查,如果接收者在发送信号时不存在,则会失败

  • 退出信号的意外行为 - 当进程通过调用 erlang:exit(self(), normal) 向自身发送退出原因为 normal 的退出信号时,它将在接收到 exit 信号时终止。在所有其他情况下,当接收到退出原因 为 normal 的退出信号时,它将被丢弃。

    exit 信号,退出原因为 kill 被接收到时,采取的行动取决于信号是由于链接的进程终止而发送的,还是使用 exit/2 BIF 显式发送的。 当使用 exit/2 BIF 发送时,该信号不能被捕获,而如果该信号是由于链接发送的,则可以被捕获。

  • 分布式阻塞信令 当通过分布式通道发送信号时,即使信号应该异步发送,发送进程也可能会被挂起。这是由于通道上的内置流量控制造成的,这种流量控制或多或少一直存在。当通道的输出缓冲区大小达到分布式缓冲区繁忙限制时,在该通道上发送的进程将被挂起,直到缓冲区大小缩小到限制以下。

    根据缓冲区变满的原因,挂起的进程恢复所需的时间可能会有很大差异。例如,这可能导致对 erpc:call() 的调用的超时被显著延迟。

    由于此功能已经存在很长时间,因此无法删除它,但是可以使用 process_flag(async_dist, Bool) 在每个进程级别启用完全异步的分布式信令, 这可以用来解决由于阻塞信令而出现的问题。但是,请注意,您需要确保为使用完全异步分布式信令发送的数据实现流量控制,或者此类数据的数量始终是有限的;否则,您可能会陷入内存使用过多的境地。

    可以通过调用 erlang:system_info(dist_buf_busy_limit) 来检查分布式缓冲区繁忙限制的大小。

前面提到的不规则性无法修复,因为它们已经成为 Erlang 的一部分太久了,并且会破坏很多现有代码。

两个进程可以相互链接。 此外,驻留在同一节点上的进程和端口也可以相互链接。 如果其中一个进程调用 link/1 BIF,并将另一个进程的进程标识符作为参数,则可以在两个进程之间创建链接。 也可以使用以下 spawn BIF 之一创建链接 spawn_link()spawn_opt()spawn_request()。 在这些情况下,spawn 操作和 link 操作将以原子方式执行。

如果链接中的一个参与者终止,它将向另一个参与者发送退出信号。 退出信号将包含已终止参与者的退出原因

可以通过调用 unlink/1 BIF 来删除链接。

链接是双向的,两个进程之间只能有一个链接。 重复调用 link() 没有效果。 任何一个相关的进程都可以创建或删除链接。

链接用于监视其他进程的行为,请参阅错误处理

错误处理

Erlang 具有进程之间进行错误处理的内置功能。 终止进程会向所有链接的进程发送退出信号,这些进程也可以终止或以某种方式处理退出。 此功能可用于构建分层程序结构,其中某些进程会监视其他进程,例如,如果它们异常终止,则重新启动它们。

有关使用此功能的 OTP 监督树的更多信息,请参阅 OTP 设计原则

发送退出信号

当进程或端口终止时,它将向所有与其链接的进程和端口发送退出信号。 退出信号将包含以下信息

  • 发送方标识符 - 已终止的进程或端口的进程或端口标识符。

  • 接收方标识符 - 将退出信号发送到的进程或端口的进程或端口标识符。

  • link 标志 - 此标志将被设置,表示退出信号是由于链接而发送的。

  • 退出原因
    终止的进程或端口的退出原因,或者原子

    • noproc,如果在先前调用 link(PidOrPort) BIF 时在设置链接时未找到进程或端口。 被标识为退出信号发送者的进程或端口将等于传递给 link/1PidOrPort 参数。
    • noconnection,如果链接的进程驻留在不同的节点上,并且节点之间的连接丢失或无法建立。 在这种情况下,被标识为退出信号发送者的进程或端口可能仍然处于活动状态。

也可以通过调用 exit(PidOrPort, Reason) BIF 显式发送退出信号。 退出信号将发送到由 PidOrPort 参数标识的进程或端口。 发送的退出信号将包含以下信息

  • 发送方标识符 - 调用 exit/2 的进程的进程标识符。

  • 接收方标识符 - 将退出信号发送到的进程或端口的进程或端口标识符。

  • link 标志 - 此标志将不会被设置,表示此退出信号不是由于链接而发送的。

  • 退出原因 - 在调用 exit/2 时作为 Reason 传递的术语。 如果 Reason 是原子 kill,则接收者无法捕获退出信号,并且在收到信号时将无条件终止。

接收退出信号

当进程接收到退出信号时会发生什么取决于

  • 接收者在收到退出信号时的trap exit 状态。
  • 退出信号的退出原因。
  • 退出信号的发送方。
  • 退出信号的 link 标志的状态。 如果设置了 link 标志,则退出信号是由于链接而发送的; 否则,退出信号是通过调用 exit/2 BIF 发送的。
  • 如果设置了 link 标志,则发生的情况还取决于在收到退出信号时链接是否仍然有效

基于以上状态,当进程收到退出信号时,将发生以下情况

  • 如果以下情况,则退出信号将被静默丢弃

    • 设置了退出信号的 link 标志,并且相应的链接已停用。
    • 退出信号的退出原因是原子 normal,接收者没有捕获退出,并且接收者和发送者不是同一个进程。
  • 如果以下情况,则接收进程将被终止

    • 未设置退出信号的 link 标志,并且退出信号的退出原因是原子 kill。 接收进程将以原子 killed 作为退出原因终止。
    • 接收者没有捕获退出,并且退出原因不是原子 normal。 此外,如果设置了退出信号的 link 标志,则链接也需要处于活动状态,否则退出信号将被丢弃。 接收进程的退出原因将等于退出信号的退出原因。 请注意,如果设置了 link 标志,则退出原因 kill不会转换为 killed
    • 退出信号的退出原因是原子 normal,并且退出信号的发送者与接收者是同一个进程。 在这种情况下,不能设置 link 标志。 接收进程的退出原因将是原子 normal
  • 如果接收者正在捕获退出,则退出信号将转换为消息信号并添加到接收者的消息队列的末尾,退出信号的 link 标志为

    • 未设置,并且信号的退出原因不是原子 kill
    • 设置,并且相应的链接处于活动状态。 请注意,在这种情况下,退出原因 kill不会终止进程,并且它将不会转换为 killed

    转换后的消息将采用 {'EXIT', SenderID, Reason} 的形式,其中 Reason 等于退出信号的退出原因,并且 SenderID 是发送退出信号的进程或端口的标识符。

监视器

除了链接之外,另一种选择是监视器。进程 Pid1 可以通过调用 BIF erlang:monitor(process, Pid2)Pid2 创建一个监视器。该函数返回一个引用 Ref

如果 Pid2 以退出原因 Reason 终止,则会向 Pid1 发送一条 'DOWN' 消息。

{'DOWN', Ref, process, Pid2, Reason}

如果 Pid2 不存在,则会立即发送 'DOWN' 消息,并将 Reason 设置为 noproc

监视器是单向的。重复调用 erlang:monitor(process, Pid) 会创建多个独立的监视器,并且每个监视器在 Pid 终止时都会发送一条 'DOWN' 消息。

可以通过调用 erlang:demonitor(Ref) 来移除监视器。

可以为具有注册名称的进程创建监视器,也可以在其他节点上创建。

进程字典

每个进程都有自己的进程字典,可以通过调用以下 BIF 来访问