Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit32d563e

Browse files
committed
update process
1 parent413d53e commit32d563e

File tree

1 file changed

+113
-56
lines changed

1 file changed

+113
-56
lines changed

‎11-process.md

Lines changed: 113 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,30 @@
11
11-进程
22
=======
33

4-
Elixir中,所有代码都在进程中执行。进程彼此独立,一个接一个并发执行,彼此通过消息传递来沟通
5-
进程不仅仅是Elixir中并发的基础,也是Elixir创建分布式、高容错程序的本质。
4+
Elixir里所有代码都在进程中执行。进程彼此独立,并发执行,通过传递消息(message)进行沟通
5+
进程不仅仅是Elixir并发编程的基础,也是Elixir创建分布式、高容错程序的本质。
66

7-
Elixir的进程和操作系统中的进程不可混为一谈
8-
Elixir的进程,在CPU和内存使用上,是极度轻量级的(不同于其它语言中的线程)。
9-
因此,同时运行着数十万、百万个进程也并不是罕见的事。
7+
Elixir的进程和传统操作系统中的进程不可混为一谈
8+
Elixir的进程在CPU和内存使用上,是极度轻量级的(但不同于其它语言中的线程)。
9+
正因如此,同时运行着数十万、百万个进程也并不是罕见的事。
1010

11-
本章将讲解如何派生新进程,以及在进程间如何发送和接受消息等基本知识
11+
本章将讲解如何派生新进程,以及在不同进程间发送和接受消息等基本知识
1212

13-
##11.1-进程派生
13+
##进程派生(spawning)
14+
15+
派生(spawning)一个新进程的方法是使用自动导入(kernel模块)的```spawn/1```函数:
1416

15-
派生(spawning)一个新进程的方法是使用自动导入(kernel函数)的```spawn/1```函数:
1617
```elixir
1718
iex> spawnfn->1+2end
1819
#PID<0.43.0>
1920
```
2021

2122
函数```spawn/1```接收一个_函数_作为参数,在其派生出的进程中执行这个函数。
2223

23-
注意spawn/1返回一个PID(进程标识)。在这个时候,这个派生的进程很可能已经结束。
24+
注意,```spawn/1```返回一个PID(进程标识)。在这个时候,这个派生的进程很可能已经结束。
25+
2426
派生的进程执行完函数后便会结束:
27+
2528
```elixir
2629
iex> pid= spawnfn->1+2end
2730
#PID<0.44.0>
@@ -44,8 +47,10 @@ true
4447

4548
可以发送和接收消息,让进程变得越来越有趣。
4649

47-
##11.2-发送和接收
50+
##发送和接收(消息)
51+
4852
使用```send/2```函数发送消息,用```receive/1```接收消息:
53+
4954
```elixir
5055
iex> sendself(), {:hello,"world"}
5156
{:hello,"world"}
@@ -56,12 +61,13 @@ iex> receive do
5661
"world"
5762
```
5863

59-
当有消息被发给某进程,该消息就被存储在该进程的邮箱里
64+
当有消息被发给某进程,该消息就被存储在该进程的“邮箱”里
6065
语句块```receive/1```检查当前进程的邮箱,寻找匹配给定模式的消息。
61-
其中函数```receive/1```支持分支子句,如```case/2```
62-
当然也可以给子句加上卫兵表达式。
66+
函数```receive/1```支持卫兵语句(guards)及分支子句(clause)如```case/2```
67+
68+
如果找不到匹配的消息,当前进程将一直等待,直到下一条信息到达。
6369

64-
如果找不到匹配的消息,当前进程将一直等待,知道下一条信息到达。但是可以设置一个超时时间
70+
可以给等待设置一个超时时间
6571
```elixir
6672
iex>receivedo
6773
...> {:hello, msg}-> msg
@@ -85,7 +91,7 @@ iex> receive do
8591
"Got hello from #PID<0.48.0>"
8692
```
8793

88-
在shell中执行程序时,辅助函数```flush/0```很有用。它清空缓冲区,打印进程邮箱中的所有消息
94+
在shell中执行程序时,辅助函数```flush/0```很有用。它清空并打印进程邮箱中的所有消息
8995
```elixir
9096
iex> sendself(),:hello
9197
:hello
@@ -94,16 +100,25 @@ iex> flush()
94100
:ok
95101
```
96102

97-
##11.3-链接
98-
Elixir中最常用的进程派生方式是通过函数```spawn_link/1```
99-
在举例子讲解```spawn_link/1```之前,来看看如果一个进程失败了会发生什么:
103+
##链接(links)
104+
105+
Elixir中最常用的进程派生方式是通过调用函数```spawn_link/1```
106+
107+
在举例子讲解```spawn_link/1```之前,来看看如果一个进程挂掉会发生什么:
100108
```elixir
101109
iex> spawnfn->raise"oops"end
102110
#PID<0.58.0>
111+
112+
[error]Process#PID<0.58.00> raised an exception
113+
** (RuntimeError) oops
114+
:erlang.apply/2
103115
```
104116

105-
。。。啥也没发生。这时因为进程都是互不干扰的。如果我们希望一个进程中发生失败可以被另一个进程知道,我们需要链接它们。
106-
使用```spawn_link/1```函数,例子:
117+
...仅仅是打印了一些文字信息,而派生这个倒霉进程的父进程仍然全无知觉地继续运行。
118+
这是因为进程是互不干扰的。
119+
如果我们希望一个进程发生异常挂掉可以被另一个进程感知,我们需要链接它们。
120+
121+
这需要使用```spawn_link/1```函数来派生进程,例子:
107122
```elixir
108123
iex> spawn_linkfn->raise"oops"end
109124
#PID<0.60.0>
@@ -112,8 +127,9 @@ iex> spawn_link fn -> raise "oops" end
112127
:erlang.apply/2
113128
```
114129

115-
当失败发生在shell中,shell会自动终止执行,并显示失败信息。这导致我们没法看清背后过程。
116-
要弄明白链接的进程在失败时发生了什么,我们在一个脚本文件使用```spawn_link/1```并且执行和观察它:
130+
当这个失败发生在shell中,shell会自动捕获这个异常并显示格式优雅的异常信息。
131+
为了弄明白失败时真正会发生什么,让我们在一个脚本文件中使用```spawn_link/1```
132+
117133
```elixir
118134
# spawn.exs
119135
spawn_linkfn->raise"oops"end
@@ -123,35 +139,70 @@ receive do
123139
end
124140
```
125141

126-
这次,该进程在失败时把它的父进程也弄停止了,因为它们是链接的。<br/>
127-
手动链接进程:```Process.link/1```
128-
建议可以多看看[Process模块](http://elixir-lang.org/docs/stable/elixir/Process.html),里面包含很多常用的进程操作函数。
142+
执行:
143+
```elixir
144+
$ elixir spawn.exs
145+
146+
** (EXIT from#PID<0.47.0>) an exception was raised:
147+
** (RuntimeError) oops
148+
spawn.exs:1: anonymousfn/0in:elixir_compiler_0.__FILE__/1
149+
```
129150

130-
进程和链接在创建能高容错系统时扮演重要角色。在Elixir程序中,我们经常把进程链接到某“管理者”上。
131-
由这个角色负责检测失败进程,并且创建新进程取代之。因为进程间独立,默认情况下不共享任何东西。
132-
而且当一个进程失败了,也不会影响其它进程。
133-
因此这种形式(进程链接到“管理者”角色)是唯一的实现方法。
151+
这次,该进程在失败时把它的父进程也弄停止了,因为它们是链接的。
134152

153+
使用函数```spawn_link/1```是在派生时对父子进程进行链接,
154+
你还可以手动对两个进程进行链接:使用函数```Process.link/1```
155+
我们推荐可以多看看[Process模块](http://elixir-lang.org/docs/stable/elixir/Process.html),里面包含很多常用的进程操作函数。
135156

136-
其它语言通常需要我们来try-catch异常,而在Elixir中我们对此无所谓,放手任进程挂掉。
157+
进程和链接在创建能高容错系统时扮演重要角色。
158+
在Elixir程序中,我们经常把进程链接到“管理者(supervisors)”上。
159+
由这个角色负责检测失败进程,并且创建新进程取代之。这是唯一可行的方式。
160+
因为进程间独立,默认情况下不共享任何东西。一个进程失败了,不会影响其它进程的状态。
161+
162+
其它语言通常需要我们抛出/捕获异常,而在Elixir中我们可以放任进程挂掉,
137163
因为我们希望“管理者”会以更合适的方式重启系统。
138-
“要死你就快一点”是Elixir软件开发的通用哲学。
164+
“死快一点(failing fast)”是Elixir软件开发中的一个常见哲学。
165+
166+
```spawn/1``````spawn_link/1```是Elixir中创建进程的基本方式。
167+
尽管我们到目前为止都是专门调用它们,实际上大部分时间我们会使用基于它们功能的一些抽象操作。
168+
比如常见的:“任务(Tasks)”。
169+
170+
##任务(Task)
171+
172+
任务建立在进程派生函数之上,提供了更好的错误报告和内省。
173+
174+
```elixir
175+
iex(1)>Task.startfn->raise"oops"end
176+
{:ok,#PID<0.55.0>}
177+
178+
15:22:33.046 [error]Task#PID<0.55.0> started from #PID<0.53.0> terminating
179+
** (RuntimeError) oops
180+
(elixir) lib/task/supervised.ex:74:Task.Supervised.do_apply/2
181+
(stdlib) proc_lib.erl:239::proc_lib.init_p_do_apply/3
182+
Function:#Function<20.90072148/0 in :erl_eval.expr/5>
183+
Args: []
184+
```
139185

186+
我们使用```Task.start/1``````Task.start_link/1```代替```spawn/1``````spawn_link/1```
187+
返回```{:ok pid}```而不仅仅是子进程的PID。这使得任务可以在监督者树中使用。
188+
另外,任务提供了许多便利的函数,比如```Task.async/1``````Task.await/1```等,
189+
以及其它一些易于构建分布式接结构的函数。
140190

141-
在讲下一章之前,让我们来看一个Elixir中常见的创建进程的情形
191+
我们将在《高级》篇中介绍关于任务更多的函数功能。现在我们只需要知道,使用任务有更好的错误报告
142192

143-
##11.4-状态
144-
目前为止我们还没有怎么谈到状态。但是,只要你创建程序,就需要状态。
145-
例如,保存程序的配置信息,或者分析一个文件先把它保存在内存里。
146-
你怎么存储状态?
193+
##状态(state)
147194

195+
目前为止我们还没有怎么谈到状态。但是如果你需要构建一个程序它需要状态,比如保存程序的配置信息,
196+
或者解析一个文件先把它保存在内存里,你在哪儿存储?
197+
198+
_进程_就是(最常见的一个)答案。我们可以写个进程执行无限循环,保存若干状态,
199+
通过收发消息维护和改变这些状态值。
200+
举个例子,我们写一个程序模块,创建一个提供键值对存储服务的进程:
148201

149-
进程就是(最常见的)答案。我们可以写无限循环的进程,保存一个状态,然后通过收发信息来告知或改变该状态。
150-
例如,写一个模块文件,用来创建一个提供k-v仓储服务的进程:
151202
```elixir
152203
defmoduleKVdo
153-
defstartdo
154-
{:ok,spawn_link(fn->loop(%{})end)}
204+
defstart_linkdo
205+
Task.start_link(fn->loop(%{})end)
155206
end
156207

157208
defploop(map)do
@@ -164,14 +215,15 @@ defmodule KV do
164215
end
165216
end
166217
end
218+
167219
```
168220

169-
注意```start```函数简单地派生一个新进程,这个进程以一个空的图为参数,执行```loop/1```函数。
170-
这个```loop/1```函数等待消息,并且针对每个消息执行合适的操作。
171-
加入受到一个```:get```消息,它把消息发回给调用者,然后再次调用自身```loop/1```,等待新消息。
172-
当受到```:put```消息,它便用一个新版本的图变量(里面的k-v更新了)再次调用自身
221+
注意```start_link```函数启动一个新的进程。这个进程以一个空的图(map)为参数,执行```loop/1```函数。
222+
启动后,```loop/1```函数等待消息,并且针对每个消息执行合适的操作。
223+
如果收到```:get```消息,它会把消息发回给调用者,然后再次调用自身```loop/1```,等待新消息。
224+
如果收到```:put```消息,它便用一个新版本的图变量(里面保存了新的键/值)再次调用```loop/1```
173225

174-
执行一下试试
226+
执行一下```iex kv.exs```
175227
```elixir
176228
iex> {:ok, pid}=KV.start
177229
#PID<0.62.0>
@@ -182,7 +234,8 @@ nil
182234
```
183235

184236
一开始进程内的图变量是没有键值的,所以发送一个```:get```消息并且刷新当前进程的收件箱,返回nil。
185-
下面再试试发送一个```:put```消息:
237+
下面发送一个```:put```消息再试一次:
238+
186239
```elixir
187240
iex> send pid, {:put,:hello,:world}
188241
#PID<0.62.0>
@@ -192,10 +245,10 @@ iex> flush
192245
:world
193246
```
194247

195-
注意进程是怎么保持一个状态的:我们通过同该进程收发消息来获取和更新这个状态
196-
事实上,任何进程只要知道该进程的PID,都能读取和修改状态。
248+
注意这个进程是怎么保持状态信息的:我们通过向该进程发送消息来获取和更新状态
249+
事实上,其它任何进程只要知道这个进程的PID,都能读取和修改状态。
197250

198-
还可以注册这个PID,给它一个名称。这使得人人都知道它的名字,并通过名字来向它发送消息
251+
还可以注册这个PID,给它一个名称。这使得我们可以通过名字来向它发送消息
199252
```elixir
200253
iex>Process.register(pid,:kv)
201254
true
@@ -205,9 +258,11 @@ iex> flush
205258
:world
206259
```
207260

208-
使用进程维护状态,以及注册进程都是Elixir程序非常常用的方式。
209-
但是大多数时间我们不会自己实现,而是使用Elixir提供的抽象实现。
210-
例如,Elixir提供的[agent](http://elixir-lang.org/docs/stable/elixir/Agent.html)就是一种维护状态的简单的抽象实现:
261+
使用进程维护状态、进程名称注册都是构建Elixir应用的常见模式。
262+
但是大多数时间我们不会自己实现这样的模式,而是使用Elixir已经提供的抽象实现。
263+
264+
例如,Elixir提供的[agent](http://elixir-lang.org/docs/stable/elixir/Agent.html)就是一个状态维护进程的简单实现:
265+
211266
```elixir
212267
iex> {:ok, pid}=Agent.start_link(fn-> %{}end)
213268
{:ok,#PID<0.72.0>}
@@ -217,7 +272,9 @@ iex> Agent.get(pid, fn map -> Map.get(map, :hello) end)
217272
:world
218273
```
219274

220-
```Agent.start/2```方法加一个一个```:name```选项可以自动为其注册一个名字。
221-
除了agents,Elixir还提供了创建通用服务器(generic servers,称作GenServer)、
222-
通用时间管理器以及事件处理器(又称GenEvent)的API。
223-
这些,连同“管理者”树,都可以在Mix和OTP手册里找到详细说明。
275+
```Agent.start/2```方法加上```:name```选项,可以自动为其注册一个名字。
276+
277+
除了agents,Elixir还提供了一套API来创建通用服务器(generic servers,称作GenServer),任务等。
278+
这些都是建立在进程概念之上的实现。其它概念,包括“管理者”树,都可以在《高级》篇里找到更详细的说明。
279+
280+
下一章将介绍Elixir语言的I/O世界。

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp