Blog Categories
Ad Space
Twitter Updates
    follow me on Twitter
    Currently Reading
    Powered by Squarespace
    Git Projects
    « Setting up a Ruby 1.9 Sandbox on Leopard | Main | Erlang Introduction (For the Ruby Guy) part 2 »
    Sunday
    17Aug

    Erlang Introduction (For the Ruby Guy) part 3

    Processes

    In Erlang you world revolves around processes. Not to be confused with OS processes. Here we are talking about highly light weight threads that can quickly be started and shut down. These are a lot faster and lighter that threads you may be used to from Ruby and you will use them quite a lot more than usually happens in Ruby. The best part is that this great power of Erlang is one of the simplest things to do. You just simply use the spawn(module, function, [parameters]) command to start a function and it starts it as a process. Really thats it. Lets see an example of this by starting the recurse command from part 2: P1 = spawn(demo,recurse,[[1,2,3]]).. Now this process started and ended within a second but imagine if this would have been some call to some webserver taking up to a few seconds. There the process would have done it's thing and the creating function continues doing it's thing without having to hang around for results.

    This is how you should do your Erlang programs. You should avoid as much as you can having your program waiting for some action to complete. Instead you should have a bunch of processes communicating with each other.

    Sending and Receiving Messages

    Now. It's not enough in most cases to just spawn the process and let it run until it finishes. Often you must talk to that process and as they do not share memory with each other they need to talk to each other by passing messages. Luckily this also is as simple as it gets.

    The return value of a spawn function is the Pid of the Process. In the example before we bound the Pid to variable P1. We then could just send a message to it simply by saying P1 ! [1,2,3,4].. If we expect a response back we often send our own Pid within the message and we can get the Pid of current process with the command self(). In that case the message might look like P1 ! {helloworld, self()}..

    It's no use sending a message if the receiving process is not listening. Enter the receive consturct. The receiver waits for a message and when it receives that message it tries matching it to a pattern and runs the corresponding actions. Lets look at a receive block.

    receive
      [Head|TheRest] ->
        io:format("Got a list with the head: ~p ~n",[Head]);
      {helloworld, CallingPid} ->
        io:format("Got a hello world. Saying hello back. ~p ~n"),
        CallingPid ! {response, "Hi Back"};
      MatchAll ->
        io:format("Error: Dont know what do do with ~p ~n",[MatchAll])
    end.
    

    Here we have 3 possible messages to receive. This works in general much like when we define multiple versions of the same function.

    Registering

    One thing that I will mention before giving an example of using processes is the concept of registering process under a name. Often it's not convienient to store a Pid in a variable since variables only exist within a function. There we register the Pid under a name with the command register(Name, Pid). For example we could call register(servername, P1). and from now on we can always make the call servername ! {helloworld, self()}. from anywhere inside that node.

    A Simple Example of Processes

    Lets take a look at an example code. Lets open our demo.erl from Part 2 and add start_server/0, start_server/0, remote_convert/1, server_loop/0 to the exports. Go to the end of the file and enter the following code:

    start_server() ->
      Pid = spawn(demo, server_loop, []),
      register(converter, Pid).
    
    stop_server() ->
      converter ! shutdown
      unregister(converter).
    
    server_loop() ->
      receive
        {convert, cm, Value, CallingPid} ->
          CallingPid ! {inch, Value / 2.54},
          server_loop();
        {convert, inch, Value, CallingPid} ->
          CallingPid ! {cm, Value * 2.54},
          server_loop();
        shutdown ->
          true;
        MatchAll ->
          io:format("Got a message I don't understand. ~n"),
          server_loop()
      end.
    
    remote_convert({Unit, Value}) ->
      converter ! {convert, Unit, Value, self()},
      receive
        {NewUnit, NewValue} ->
          io:format("Result: ~p ~p~n", [NewUnit,NewValue]);
        error ->
          ok
      end.
    

    Lets take a look what happens here. We start by compiling and running demo:start_server(). that spawns server_loop/0 as a process and registers the Pid as converter. Next up is stop_server/0 that sends a message to the server asking it to shutdown. Third is server_loop/0 has a receive block that waits for 4 matching messages. First 2 are tuples requesting a unit conversion. It converts and sends a message back and then calls itself so it can wait for the next message. The shutdown message really does nothing except not calling for the loop to repeat. MatchAll will handle all other messages and write out an error.

    remote_convert/1 then manages calling the server and then waits for a message back. Lets try it out by running demo:remote_convert({inch,1}). and you should get a message back.

    Reader Comments (2)

    Thank you for the tutorial.

    In your last paragraph you said Lets try it out by running demo:convert({inch,1}), it should demo:remote_convert({inch, 1}).

    Nov 3, 2008 at 5:13 | Unregistered Commenterheru

    A great series of articles for a beginner such as myself.
    Well explained and very helpful.

    Thanks!

    Jan 19, 2009 at 6:19 | Unregistered Commenterspook

    PostPost a New Comment

    Enter your information below to add a new comment.

    My response is on my own website »
    Author Email (optional):
    Author URL (optional):
    Post:
     
    Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>
    Fork me on GitHub