Example code: echo.erl, mandelbrot.erl, rowactor.erl, mandelbrotactor.erl
Concurrency in Erlang
Concurrency in Erlang is expressed using processes. A process is an independent thread of control that does not share memory with any other process: so, processes are isolated from each other. Processes communicate with each other by sending messages.
Processes in Erlang
A process is started by calling a function that takes no parameters. Ths function should use the receive construct to wait for a message to arrive, process the message, and then call itself recursively. (As long as the recursive call is in tail position, this will not cause growth of the call stack.)
Here is a very simple actor: one that simply prints out any messages it receives:
The receive construct does pattern matching on received messages. In the echo actor above, the only pattern is the variable Any, which will match any value sent to the process.
Example of compiling and running this process:
1> c(echo). {ok,echo} 2> EchoPid = spawn(fun echo:loop/0). <0.39.0> 3> EchoPid ! "Hello". Echo: [72,101,108,108,111] "Hello" 4> EchoPid ! {hey, there}. Echo: {hey,there} {hey,there}
Erlang processes are created by the built-in spawn function, and identified by process ids. To send a message to a process, the syntax is
ProcessId ! message
Example: The Mandelbrot Set using Erlang processes
As a complete example, let’s do the Mandelbrot set computation using Erlang actors. First, we need some functions to do complex arithmetic and to compute iteration counts for a row of complex numbers:
Next, an actor process which receives messages specifying rows of iteration counts to be computed, computes them, and sends the results back to a result collector process:
Note one interesting detail: the first message the row actor process should receive is a tuple of the form
{resultcollectorpid, Pid}
which specifies the process id of the process to which computed row results should be sent. Once this message is received, all subsequent calls to loopwork will have this process id available.
Finally, an actor which receives a message describing a region of the complex plane, creates row actors, sends work to the row actors, and waits for row results to be sent back:
Note that completed row results are just printed out using io:format, in whatever order they arrive.
Example run:
1> c(mandelbrot). {ok,mandelbrot} 2> c(rowactor). {ok,rowactor} 3> c(mandelbrotactor). {ok,mandelbrotactor} 4> Pid = spawn(fun mandelbrotactor:loop/0). <0.49.0> 5> Pid ! {start, -2, 2, -2, 2, 10, 10, 3}. {start,-2,2,-2,2,10,10,3} 2: [1,2,2,3,3,3,2,2,2,2] 0: [1,1,1,1,1,2,1,1,1,1] 3: [1,3,3,4,6,18,4,2,2,2] 1: [1,1,2,2,2,2,2,2,2,1] 5: [1000,1000,1000,1000,1000,1000,7,3,2,2] 8: [1,2,2,3,3,3,2,2,2,2] 6: [1,3,7,7,1000,1000,9,3,2,2] 9: [1,1,2,2,2,2,2,2,2,1] 4: [1,3,7,7,1000,1000,9,3,2,2] 7: [1,3,3,4,6,18,4,2,2,2]
In this example run, we used three row actor processes to compute 10 rows. Each row actor computes every nth row, where n is the number of processes.
As you can see, the row results do not come back in sorted order, so some additional work is needed to put them in order.