-
Notifications
You must be signed in to change notification settings - Fork 5
/
more-on-multiprocessing.html
398 lines (314 loc) · 23.3 KB
/
more-on-multiprocessing.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta name="keywords" content="Erlang, multiprocessing, process, state, message, timeout, selective receive, mailbox, receive, recursion" />
<meta name="description" content="Additional concepts about multiprocessing in Erlang. Includes examples on how to keep state, avoid locks with timeouts and how to handle selective receives." />
<meta name="google-site-verification" content="mi1UCmFD_2pMLt2jsYHzi_0b6Go9xja8TGllOSoQPVU" />
<link rel="stylesheet" type="text/css" href="static/css/screen.css" media="screen" />
<link rel="stylesheet" type="text/css" href="static/css/sh/shCore.css" media="screen" />
<link rel="stylesheet" type="text/css" href="static/css/sh/shThemeLYSE2.css" media="screen" />
<link rel="stylesheet" type="text/css" href="static/css/print.css" media="print" />
<link href="rss" type="application/rss+xml" rel="alternate" title="LYSE news" />
<link rel="icon" type="image/png" href="favicon.ico" />
<link rel="apple-touch-icon" href="static/img/touch-icon-iphone.png" />
<link rel="apple-touch-icon" sizes="72x72" href="static/img/touch-icon-ipad.png" />
<link rel="apple-touch-icon" sizes="114x114" href="static/img/touch-icon-iphone4.png" />
<title>More On Multiprocessing | Learn You Some Erlang for Great Good!</title>
</head>
<body>
<div id="wrapper">
<div id="header">
<h1>Learn you some Erlang</h1>
<span>for great good!</span>
</div> <!-- header -->
<div id="menu">
<ul>
<li><a href="content.html" title="Home">Home</a></li>
<li><a href="faq.html" title="Frequently Asked Questions">FAQ</a></li>
<li><a href="rss" title="Latest News">RSS</a></li>
<li><a href="static/erlang/learn-you-some-erlang.zip" title="Source Code">Code</a></li>
</ul>
</div><!-- menu -->
<div id="content">
<div class="noscript"><noscript>Hey there, it appears your Javascript is disabled. That's fine, the site works without it. However, you might prefer reading it with syntax highlighting, which requires Javascript!</noscript></div>
<h2>More On Multiprocessing</h2>
<h3><a class="section" name="state-your-state">State Your State</a></h3>
<img class="right" src="static/img/turkey.png" width="140" height="157" alt="a roasted turkey leg" />
<p>The examples shown in the previous chapter were all right for demonstrative purposes, but you won't go far with only that in your toolkit. It's not that the examples were bad, it's mostly that there is not a huge advantage to processes and actors if they're just functions with messages. To fix this, we have to be able to hold state in a process.</p>
<p>Let's first create a function in a new <a class="source" href="static/erlang/kitchen.erl">kitchen.erl</a> module that will let a process act like a fridge. The process will allow two operations: storing food in the fridge and taking food from the fridge. It should only be possible to take food that has been stored beforehand. The following function can act as the base for our process:</p>
<pre class="brush:erl">
-module(kitchen).
-compile(export_all).
fridge1() ->
receive
{From, {store, _Food}} ->
From ! {self(), ok},
fridge1();
{From, {take, _Food}} ->
%% uh....
From ! {self(), not_found},
fridge1();
terminate ->
ok
end.
</pre>
<p>Something's wrong with it. When we ask to store the food, the process should reply with <samp>ok</samp>, but there is nothing actually storing the food; <code>fridge1()</code> is called and then the function starts from scratch, without state. You can also see that when we call the process to take food from the fridge, there is no state to take it from and so the only thing to reply is <samp>not_found</samp>. In order to store and take food items, we'll need to add state to the function.</p>
<p>With the help of recursion, the state to a process can then be held entirely in the parameters of the function. In the case of our fridge process, a possibility would be to store all the food as a list, and then look in that list when someone needs to eat something:</p>
<pre class="brush:erl">
fridge2(FoodList) ->
receive
{From, {store, Food}} ->
From ! {self(), ok},
fridge2([Food|FoodList]);
{From, {take, Food}} ->
case lists:member(Food, FoodList) of
true ->
From ! {self(), {ok, Food}},
fridge2(lists:delete(Food, FoodList));
false ->
From ! {self(), not_found},
fridge2(FoodList)
end;
terminate ->
ok
end.
</pre>
<p>The first thing to notice is that <code>fridge2/1</code> takes one argument, <var>FoodList</var>. You can see that when we send a message that matches <code>{From, {store, Food}}</code>, the function will add <var>Food</var> to <var>FoodList</var> before going. Once that recursive call is made, it will then be possible to retrieve the same item. In fact, I implemented it there. The function uses <code><a class="docs" href="http://erldocs.com/17.3/lists.html#member/2" title="The lists club counts me as a member">lists:member/2</a></code> to check whether <var>Food</var> is part of <var>FoodList</var> or not. Depending on the result, the item is sent back to the calling process (and removed from <var>FoodList</var>) or <samp>not_found</samp> is sent back otherwise:</p>
<pre class="brush:eshell">
1> c(kitchen).
{ok,kitchen}
2> Pid = spawn(kitchen, fridge2, [[baking_soda]]).
<0.51.0>
3> Pid ! {self(), {store, milk}}.
{<0.33.0>,{store,milk}}
4> flush().
Shell got {<0.51.0>,ok}
ok
</pre>
<p>Storing items in the fridge seems to work. We'll try with some more stuff and then try to take it from the fridge.</p>
<pre class="brush:eshell">
5> Pid ! {self(), {store, bacon}}.
{<0.33.0>,{store,bacon}}
6> Pid ! {self(), {take, bacon}}.
{<0.33.0>,{take,bacon}}
7> Pid ! {self(), {take, turkey}}.
{<0.33.0>,{take,turkey}}
8> flush().
Shell got {<0.51.0>,ok}
Shell got {<0.51.0>,{ok,bacon}}
Shell got {<0.51.0>,not_found}
ok
</pre>
<p>As expected, we can take bacon from the fridge because we have put it in there first (along with the milk and baking soda), but the fridge process has no turkey to find when we request some. This is why we get the last <code>{<0.51.0>,not_found}</code> message.</p>
<h3><a class="section" name="secret-messages">We love messages, but we keep them secret</a></h3>
<p>Something annoying with the previous example is that the programmer who's going to use the fridge has to know about the protocol that's been invented for that process. That's a useless burden. A good way to solve this is to abstract messages away with the help of functions dealing with receiving and sending them:</p>
<pre class="brush:erl">
store(Pid, Food) ->
Pid ! {self(), {store, Food}},
receive
{Pid, Msg} -> Msg
end.
take(Pid, Food) ->
Pid ! {self(), {take, Food}},
receive
{Pid, Msg} -> Msg
end.
</pre>
<p>Now the interaction with the process is much cleaner:</p>
<pre class="brush:eshell">
9> c(kitchen).
{ok,kitchen}
10> f().
ok
11> Pid = spawn(kitchen, fridge2, [[baking_soda]]).
<0.73.0>
12> kitchen:store(Pid, water).
ok
13> kitchen:take(Pid, water).
{ok,water}
14> kitchen:take(Pid, juice).
not_found
</pre>
<p>We don't have to care about how the messages work anymore, if sending <code>self()</code> or a precise atom like <code>take</code> or <code>store</code> is needed: all that's needed is a pid and knowing what functions to call. This hides all of the dirty work and makes it easier to build on the fridge process.</p>
<p>One thing left to do would be to hide that whole part about needing to spawn a process. We dealt with hiding messages, but then we still expect the user to handle the creation of the process. I'll add the following <code>start/1</code> function:</p>
<pre class="brush:erl">
start(FoodList) ->
spawn(?MODULE, fridge2, [FoodList]).
</pre>
<img class="right" src="static/img/abstraction.png" width="400" height="160" alt="Two tin cans with a string, where the tin cans somehow represent the abstraction layer between the vibrating string and the voice" />
<p>Here, <code>?MODULE</code> is a macro returning the current module's name. It doesn't look like there are any advantages to writing such a function, but there really are some. The essential part of it would be consistency with the calls to <code>take/2</code> and <code>store/2</code>: everything about the fridge process is now handled by the <a class="source" href="static/erlang/kitchen.erl">kitchen</a> module. If you were to add logging when the fridge process is started or start a second process (say a freezer), it would be really easy to do inside our <code>start/1</code> function. However if the spawning is left for the user to do through <code>spawn/3</code>, then every place that starts a fridge now needs to add the new calls. That's prone to errors and errors suck.</p>
<p>Let's see this function put to use:</p>
<pre class="brush:eshell">
15> f().
ok
16> c(kitchen).
{ok,kitchen}
17> Pid = kitchen:start([rhubarb, dog, hotdog]).
<0.84.0>
18> kitchen:take(Pid, dog).
{ok,dog}
19> kitchen:take(Pid, dog).
not_found
</pre>
<p>Yay! The dog has got out of the fridge and our abstraction is complete!</p>
<h3><a class="section" name="time-out">Time Out</a></h3>
<p>Let's try a little something with the help of the command <code>pid(A,B,C)</code>, which lets us change the 3 integers <var>A</var>, <var>B</var> and <var>C</var> into a pid. Here we'll deliberately feed <code>kitchen:take/2</code> a fake one:</p>
<pre class="brush:eshell">
20> kitchen:take(pid(0,250,0), dog).
</pre>
<p>Woops. The shell is frozen. This happened because of how <code>take/2</code> was implemented. To understand what goes on, let's first revise what happens in the normal case:</p>
<ol>
<li>A message to take food is sent from you (the shell) to the fridge process;</li>
<li>Your process switches to receive mode and waits for a new message;</li>
<li>The fridge removes the item and sends it to your process;</li>
<li>Your process receives it and moves on with its life.</li>
</ol>
<img class="right" src="static/img/hourglass.png" width="83" height="161" alt="Hourglass" title="'tok tok' sounds more like 'scrprptprptpprptrptpt' in the context of hourglasses" />
<p>And here's what happens when the shell freezes:</p>
<ol>
<li>A message to take food is sent from you (the shell) to an unknown process;</li>
<li>Your process switches to receive mode and waits for a new message;</li>
<li>The unknown process either doesn't exist or doesn't expect such a message and does nothing with it;</li>
<li>Your shell process is stuck in receive mode.</li>
</ol>
<p>That's annoying, especially because there is no error handling possible here. Nothing illegal happened, the program is just waiting. In general, anything dealing with asynchronous operations (which is how message passing is done in Erlang) needs a way to give up after a certain period of time if it gets no sign of receiving data. A web browser does it when a page or image takes too long to load, you do it when someone takes too long before answering the phone or is late at a meeting. Erlang certainly has an appropriate mechanism for that, and it's part of the <code>receive</code> construct:</p>
<pre class="brush:erl">
receive
Match -> Expression1
after Delay ->
Expression2
end.
</pre>
<p>The part in between <code>receive</code> and <code>after</code> is exactly the same that we already know. The <code>after</code> part will be triggered if as much time as <var>Delay</var> (an integer representing milliseconds) has been spent without receiving a message that matches the <var>Match</var> pattern. When this happens, <var>Expression2</var> is executed.</p>
<p>We'll write two new interface functions, <code>store2/2</code> and <code>take2/2</code>, which will act exactly like <code>store/2</code> and <code>take/2</code> with the exception that they will stop waiting after 3 seconds:</p>
<pre class="brush:erl">
store2(Pid, Food) ->
Pid ! {self(), {store, Food}},
receive
{Pid, Msg} -> Msg
after 3000 ->
timeout
end.
take2(Pid, Food) ->
Pid ! {self(), {take, Food}},
receive
{Pid, Msg} -> Msg
after 3000 ->
timeout
end.
</pre>
<p>Now you can unfreeze the shell with <code><a class="chapter" href="starting-out.html#shell-commands">^G</a></code> and try the new interface functions:</p>
<pre class="brush:erl">
User switch command
--> k
--> s
--> c
Eshell V5.7.5 (abort with ^G)
1> c(kitchen).
{ok,kitchen}
2> kitchen:take2(pid(0,250,0), dog).
timeout
</pre>
<p>And now it works. </p>
<div class="note">
<p><strong>Note:</strong> I said that <code>after</code> only takes milliseconds as a value, but it is actually possible to use the atom <code>infinity</code>. While this is not useful in many cases (you might just remove the <code>after</code> clause altogether), it is sometimes used when the programmer can submit the wait time to a function where receiving a result is expected. That way, if the programmer really wants to wait forever, he can.</p>
</div>
<p>There are uses to such timers other than giving up after too long. One very simple example is how the <code><a class="docs" href="http://erldocs.com/17.3/timer.html#sleep/1" title="a lullaby for your code">timer:sleep/1</a></code> function we've used before works. Here's how it is implemented (let's put it in a new <a class="source" href="static/erlang/multiproc.erl">multiproc.erl</a> module):</p>
<pre class="brush:erl">
sleep(T) ->
receive
after T -> ok
end.
</pre>
<p>In this specific case, no message will ever be matched in the <code>receive</code> part of the construct because there is no pattern. Instead, the <code>after</code> part of the construct will be called once the delay <var>T</var> has passed.</p>
<p>Another special case is when the timeout is at 0:</p>
<pre class="brush:erl">
flush() ->
receive
_ -> flush()
after 0 ->
ok
end.
</pre>
<p>When that happens, the Erlang VM will try and find a message that fits one of the available patterns. In the case above, anything matches. As long as there are messages, the <code>flush/0</code> function will recursively call itself until the mailbox is empty. Once this is done, the <code>after 0 -> ok</code> part of the code is executed and the function returns.</p>
<h3><a class="section" name="selective-receives">Selective Receives</a></h3>
<p>This 'flushing' concept makes it possible to implement a <em> selective receive</em> which can give a priority to the messages you receive by nesting calls:</p>
<pre class="brush:erl">
important() ->
receive
{Priority, Message} when Priority > 10 ->
[Message | important()]
after 0 ->
normal()
end.
normal() ->
receive
{_, Message} ->
[Message | normal()]
after 0 ->
[]
end.
</pre>
<p>This function will build a list of all messages with those with a priority above 10 coming first:</p>
<pre class="brush:eshell">
1> c(multiproc).
{ok,multiproc}
2> self() ! {15, high}, self() ! {7, low}, self() ! {1, low}, self() ! {17, high}.
{17,high}
3> multiproc:important().
[high,high,low,low]
</pre>
<p>Because I used the <code>after 0</code> bit, every message will be obtained until none is left, but the process will try to grab all those with a priority above 10 before even considering the other messages, which are accumulated in the <code>normal/0</code> call.</p>
<p>If this practice looks interesting, be aware that is is sometimes unsafe due to the way selective receives work in Erlang.</p>
<p>When messages are sent to a process, they're stored in the mailbox until the process reads them and they match a pattern there. As said in the <a class="chapter" href="the-hitchhikers-guide-to-concurrency.html">previous chapter</a>, the messages are stored in the order they were received. This means every time you match a message, it begins by the oldest one.</p>
<p>That oldest message is then tried against every pattern of the <code>receive</code> until one of them matches. When it does, the message is removed from the mailbox and the code for the process executes normally until the next <code>receive</code>. When this next <code>receive</code> is evaluated, the VM will look for the oldest message currently in the mailbox (the one after the one we removed), and so on.</p>
<img class="center explanation" src="static/img/msg-match.png" width="260" height="329" alt="Visual explanation of how message matching is done when a message from the mailbox does match" title="look at them arrows! woo!" />
<p>When there is no way to match a given message, it is put in a <em>save queue</em> and the next message is tried. If the second message matches, the first message is put back on top of the mailbox to be retried later.</p>
<img class="center explanation" src="static/img/msg-nomatch.png" width="347" height="339" alt="Visual explanation of how messages that won't match are moved back and forth from the mailbox to a save queue" title="graphs aren't my forte" />
<p>This lets you only care about the messages that are useful. Ignoring some messages to handle them later in the manner described above is the essence of <em>selective receives</em>. While they're useful, the problem with them is that if your process has a lot of messages you never care about, reading useful messages will actually take longer and longer (and the processes will grow in size too).</p>
<p>In the drawing above, imagine we want the 367th message, but the first 366 are junk ignored by our code. To get the 367th message, the process needs to try to match the 366 first ones. Once it's done and they've all been put in the queue, the 367th message is taken out and the first 366 are put back on top of the mailbox. The next useful message could be burrowed much deeper and take even longer to be found.</p>
<p>This kind of receive is a frequent cause of performance problems in Erlang. If your application is running slow and you know there are lots of messages going around, this could be the cause.</p>
<p>If such selective receives are effectively causing a massive slowdown in your code, the first thing to do is to ask yourself is why you are getting messages you do not want. Are the messages sent to the right processes? Are the patterns correct? Are the messages formatted incorrectly? Are you using one process where there should be many? Answering one or many of these questions could solve your problem.</p>
<p>Because of the risks of having useless messages polluting a process' mailbox, Erlang programmers sometimes take a defensive measure against such events. A standard way to do it might look like this:</p>
<pre class="brush:erl">
receive
Pattern1 -> Expression1;
Pattern2 -> Expression2;
Pattern3 -> Expression3;
...
PatternN -> ExpressionN;
Unexpected ->
io:format("unexpected message ~p~n", [Unexpected])
end.
</pre>
<p>What this does is make sure any message will match at least one clause. The <var>Unexpected</var> variable will match anything, take the unexpected message out of the mailbox and show a warning. Depending on your application, you might want to store the message into some kind of logging facility where you will be able to find information about it later on: if the messages are going to the wrong process, it'd be a shame to lose them for good and have a hard time finding why that other process doesn't receive what it should.</p>
<p>In the case you do need to work with a priority in your messages and can't use such a catch-all clause, a smarter way to do it would be to implement a <a class="external" href="http://en.wikipedia.org/wiki/Min-heap" title="Min heap description, but no implementation. If you want one, I could try to write one">min-heap</a> or use the <code>gb_trees</code> module and dump every received message in it (make sure to put the priority number first in the key so it gets used for sorting the messages). Then you can just search for the <code><a class="docs" href="http://erldocs.com/17.3/gb_trees.html#take_smallest/1" title="oh erldocs!">smallest</a></code> or <code><a class="docs" href="http://erldocs.com/17.3/gb_trees.html#take_largest/1" title="The element that's the most on the right">largest</a></code> element in the data structure according to your needs.</p>
<p>In most cases, this technique should let you receive messages with a priority more efficiently than selective receives. However, it could slow you down if most messages you receive have the highest priority possible. As usual, the trick is to profile and measure before optimizing.</p>
<div class="note">
<p><strong>Note:</strong> Since R14A, a new optimization has been added to Erlang's compiler. It simplifies selective receives in very specific cases of back-and-forth communications between processes. An example of such a function is <code>optimized/1</code> in <a class="source" href="static/erlang/multiproc.erl">multiproc.erl</a>.</p>
<p>To make it work, a reference (<code>make_ref()</code>) has to be created in a function and then sent in a message. In the same function, a selective receive is then made. If no message can match unless it contains the same reference, the compiler automatically makes sure the VM will skip messages received before the creation of that reference.</p>
<p>Note that you shouldn't try to coerce your code to fit such optimizations. The Erlang developers only look for patterns that are frequently used and then make them faster. If you write idiomatic code, optimizations should come to you. Not the other way around.</p>
</div>
<p>With these concepts understood, the next step will be to do error handling with multiple processes.</p>
<ul class="navigation">
<li><a href="the-hitchhikers-guide-to-concurrency.html" title="Previous chapter">< Previous</a></li>
<li><a href="contents.html" title="Index">Index</a></li>
<li><a href="errors-and-processes.html" title="Next chapter">Next ></a></li>
</ul>
</div><!-- content -->
<div id="footer">
<a href="http://creativecommons.org/licenses/by-nc-nd/3.0/" title="Creative Commons License Details"><img src="static/img/cc.png" width="88" height="31" alt="Creative Commons Attribution Non-Commercial No Derivative License" /></a>
<p>Except where otherwise noted, content on this site is licensed under a Creative Commons Attribution Non-Commercial No Derivative License</p>
</div> <!-- footer -->
</div> <!-- wrapper -->
<div id="grass" />
<script type="text/javascript" src="static/js/shCore.js"></script>
<script type="text/javascript" src="static/js/shBrushErlang2.js%3F11"></script>
<script type="text/javascript">
SyntaxHighlighter.defaults.gutter = false;
SyntaxHighlighter.all();
</script>
</body>
</html>