Skip to content

Commit b009aa3

Browse files
committed
cleaning
1 parent e7f8c69 commit b009aa3

File tree

2 files changed

+97
-49
lines changed

2 files changed

+97
-49
lines changed

README.md

+47-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,48 @@
11
# elixir_map_pmap_apmap_example
2-
Example of how to put asynchronous behavior behind an API in Elixir, using Parallel Map. Using map, pmap and an asynchronous pmap.
2+
Example of how to put asynchronous behavior behind an API in Elixir, using Parallel Map. Using map, pmap and an asynchronous pmap.
3+
4+
5+
The cool thing about this code is, as a user of the function (the caller) you don't know
6+
which version it will use. In most other languages (like Javascript), this is an
7+
exceptionally bad idea, but in Elixir this is actually a common way to solve problems.
8+
9+
"Javascript with Promises", by Daniel Parker has this warning:
10+
```
11+
WARNING
12+
Functions that invoke a callback synchronously in some cases and asynchronously in
13+
others create forks in the execution path that make your code less predictable.
14+
```
15+
16+
But we ignore this warning in Elixir :) , because there is only one execution path
17+
for a delayed job - messages which your process will handle when it wants to.
18+
19+
For example, the Logger works like this in Elixir. If the backlog of things to log is
20+
small, then it completes synchronously, otherwise it will decide to switch to
21+
asynchronous mode, just like magic.
22+
23+
24+
Here's what the code invoked looks like:
25+
```
26+
result = Parallel.map_or_pmap_or_apmap((0..10), fn(x) -> x * x end)
27+
```
28+
29+
result will either be:
30+
31+
```
32+
{:ok, result_arr} # result is available synchronously
33+
{:delayed, pid} # this is going to take a while, I'll send you a message when it is ready
34+
```
35+
36+
37+
Depending on the size of the Range passed to the map_or_pmap_or_apmap function, the
38+
work can be accomplished in one of three ways:
39+
1. For small arrays <= 100 elements in size, it just runs in process synchronously
40+
1. For medium arrays <= 1000 elements in size, it runs parallel map (multiprocess), but synchronously
41+
1. For anything bigger it runs parallel map, but does it asynchronously and send a response when it
42+
it done with the work.
43+
44+
```
45+
Parallel.handle_result(Parallel.map_or_pmap_or_apmap((0..10), fn(x) -> x * x end))
46+
Parallel.handle_result(Parallel.map_or_pmap_or_apmap((0..150), fn(x) -> x * x end))
47+
Parallel.handle_result(Parallel.map_or_pmap_or_apmap((0..1000000), fn(x) -> x * x end))
48+
```

parallel.ex

+50-48
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,19 @@ defmodule Parallel do
66
77
"""
88

9-
@doc """
10-
runs a parallel map by spawning a bunch of processes and returns array of results
11-
## Parameters
12-
- collection: a collection that can be enumerated
13-
- function: an anonymous function to apply to each element in collection
14-
15-
## Examples
16-
iex> Parallel.pmap((0..4), fn(x) -> x * x end)
17-
[0, 1, 4, 9, 16]
18-
"""
19-
def pmap(collection, function) do
20-
me = self
21-
pid_list = Enum.map(collection, fn (elem) ->
22-
spawn_link fn -> (send me, { self, function.(elem) }) end
23-
end)
24-
Enum.map(pid_list, fn (pid) ->
25-
receive do { ^pid, result } -> result end
26-
end)
27-
end
28-
29-
def pmap_wrapper(me, collection, function) do
30-
result = pmap(collection, function)
31-
send me, {self, result}
32-
end
339

3410
@doc """
35-
runs a parallel map by either a) in current process, b) synchronous multi-process or
11+
This shows a common technique in Elixir/Erlang. If something is fast, do
12+
it synchronous (either in process or multi-process), and it looks like
13+
a big problem, then do it asynchronous, which lets the caller do other
14+
work and check back later for the result.
15+
16+
Runs a parallel map by either a) in current process, b) synchronous multi-process or
3617
c) fully asynchronous multi-process, and you need to wait for result.
3718
38-
It chooses which one to run based on the size of the collection
19+
It chooses which one to run based on the size of the collection.
20+
21+
Only works with ranges currently.
3922
4023
## Parameters
4124
- range: a collection that can be enumerated
@@ -50,7 +33,7 @@ defmodule Parallel do
5033
first..last = range
5134
result = case last - first do
5235
x when x > 1000 ->
53-
IO.puts("<delayed_map>")
36+
IO.puts("<delayed_pmap>")
5437
pid = spawn_link( fn -> pmap_wrapper(me, range, function) end)
5538
{:delayed, pid}
5639
x when x > 100 ->
@@ -72,17 +55,43 @@ defmodule Parallel do
7255
result
7356
end
7457

58+
@doc """
59+
runs a parallel map by spawning a bunch of processes and returns array of results
60+
## Parameters
61+
- collection: a collection that can be enumerated
62+
- function: an anonymous function to apply to each element in collection
63+
64+
## Examples
65+
iex> Parallel.pmap((0..4), fn(x) -> x * x end)
66+
[0, 1, 4, 9, 16]
67+
"""
68+
def pmap(collection, function) do
69+
me = self
70+
pid_list = Enum.map(collection, fn (elem) ->
71+
spawn_link fn -> (send me, { self, function.(elem) }) end
72+
end)
73+
Enum.map(pid_list, fn (pid) ->
74+
receive do { ^pid, result } -> result end
75+
end)
76+
end
77+
78+
def pmap_wrapper(me, collection, function) do
79+
result = pmap(collection, function)
80+
send me, {self, result}
81+
end
82+
7583
def map(collection, function) do
7684
Enum.map(collection, function)
7785
end
7886

79-
def show_result(result) do
80-
case result do
81-
{:ok, arr} -> IO.puts(">> Running synchronously, I am waiting for results")
82-
IO.inspect(arr)
83-
{:delayed, _} -> IO.puts(">> Running asynchronously, I am not going to wait for results")
84-
end
85-
result
87+
def handle_result(result = {:delayed, pid}) do
88+
IO.puts(">> Running asynchronously, I am not going to wait for results:")
89+
waiting(pid)
90+
end
91+
92+
def handle_result(result = {:ok, arr}) do
93+
IO.puts(">> Ran synchronously, here are the results:")
94+
IO.inspect(arr)
8695
end
8796

8897
def run_fail_test do
@@ -102,25 +111,18 @@ defmodule Parallel do
102111
IO.puts "checking the 3 versions"
103112

104113
# But what if you only want to wait if it is a BIG one...
105-
Parallel.show_result(Parallel.map_or_pmap_or_apmap((0..10), fn(x) -> x * x end))
106-
Parallel.show_result(Parallel.map_or_pmap_or_apmap((0..150), fn(x) -> x * x end))
107-
result = Parallel.show_result(Parallel.map_or_pmap_or_apmap((0..1000000), fn(x) -> x * x end))
108-
109-
case result do
110-
{:delayed, pid} ->
111-
start_waiting(pid)
112-
{:ok, arr } -> IO.puts("not delayed")
113-
IO.inspect(arr)
114-
end
114+
Parallel.handle_result(Parallel.map_or_pmap_or_apmap((0..10), fn(x) -> x * x end))
115+
Parallel.handle_result(Parallel.map_or_pmap_or_apmap((0..150), fn(x) -> x * x end))
116+
Parallel.handle_result(Parallel.map_or_pmap_or_apmap((0..1000000), fn(x) -> x * x end))
115117
end
116118

117-
def start_waiting(pid) do
119+
def waiting(pid) do
118120
receive do
119-
{ ^pid, result } -> IO.puts("done")
121+
{ ^pid, result } -> IO.puts("Results have ready. Yay!:")
120122
IO.inspect(result)
121123
after
122-
1_000 -> IO.puts "still waiting..."
123-
start_waiting(pid)
124+
1_000 -> IO.puts "still waiting... more hamsters needed..."
125+
waiting(pid)
124126
end
125127
end
126128

0 commit comments

Comments
 (0)