The Actor/Message Passing System of Concurrency

Lately, a lot of research has gone into how to make concurrency easier, both conceptually and programmatically. One of the models, originally created by Erikkson for the language Erlang, is the Actor/Message Passing system. The idea behind this system is that you a dispatcher creating tasks in response to some data. Information about the tasks are bundled up into objects, and each object is sent to an actor. The actors are sitting on top of a thread pool, waiting for a task. When they get a task, it goes into their mailbox. The next time that actor checks its mailbox, it retrieves the information in the message, grabs a thread from the thread pool, and begins performing that task. When it finishes that task, it sends the output of that task either back to the dispatcher or to another actor, and it checks its mailbox again.

The above model is conceptually very simple as long as your tasks are easily broken up into repeatable chunks. For example, it would be very easy to write a log parser. I have an actor polling the log, and when a new line comes in, it sends it to one of the parser actors. The parser actor figures out what to do with it, such as printing it to a different file if it is a particularly important event, and then returns to checking its mailbox. This works wonderfully where, say, the parser must go to the web or to disk to fetch some data to complete the task. The parser gives up the thread while waiting for the network or disk to return the data, and another parser gladly picks it up and begins executing. When the waiting parser is ready to complete its task, it gets back in line for a thread in the thread pool. Eventually, the operating system will assign it a thread, and it can finish its task.

This works well for systems where you have many similar tasks to perform concurrently, such as running the same program with different parameters many times. It also works well for networked solutions, as the messages are easily encapsulated and sent over a network to a waiting receiving dispatcher.

A modern implementation that has been gaining a lot of traction lately is Scala. Scala is a functional language that runs on the JVM and uses a Python or Ruby-esque syntax. It embrasses the actor/message passing system and makes it trivial to write code that utilizes concurrency with this model. As an example, I’ve written a simple IRC bot that uses this model. Polling the socket is done by a function running on one thread. The entire body of the polling function, once we’re connected to the socket, is:

val responder = new IRCResponder(connect, ircBotNick, ircBotDescription, homeChannel)
responder.start()

while(true)
      {
      val line = in.readLine()

      if(line != null)
        {
        if(line.substring(0,4).equalsIgnoreCase("ping")) {
          val pongmsg = "pong " + line.substring(5)
          responder ! IRC_Response(pongmsg)
        }
        else {
          responder ! IRC_Message(line)
          println("SENT TO RESPONDER")
          println(line)
        }
      }
    }

Note the lines with the exclamation point. Those are the lines that send the IRC_Message object to the responder actor. Really easy, right? responder.start() tells the responder object we just created to start running as an actor. When objects are sent to the responder, different actions are taken depending on what type of object was sent.

  def act() {
    // This thread will throttle the parsing threads by only allowing one thing to write at once.
    // So when data is sent back to here, write it out in the order that it came in.
    loop {
      react {
        case message: IRC_Message =>
        // Figure out which parser to send this message to and send it
        listOfParsers(findParser()) ! message

        case response: IRC_Response =>
        // Write the response to the socket
        sendData(response.response)
      }
    }
  }

The above code is the code the Responder object uses to figure out how to handle the object sent to it. If a IRC_Message is sent to it, then find a parser (this function finds the parser with the smallest mailbox) and send it to that parser for processing. If it is an IRC_Response, then we are getting a response back from a parser, so write it to the socket. The Parser’s act function looks a lot like this, except that the only case is for IRC_Messages, and then it probes the string in that message to see how it should respond.

The code is similar for the parser:

def act() {
    loop {
      react {
        case message: IRC_Message =>
        val line = message.message.toLowerCase

          // So we've received a message from the responder, figure out how to parse it.
          if ( line contains "define" ) {
            sender ! IRC_Response(toChat(getDef(line)))
          }
          else if ( line contains "find" ) {
            sender ! IRC_Response(toChat(getTorr(line)))
          }
          else if ( line contains "roll the dice" ) {
            sender ! IRC_Response(toChat("4."))
          }
      }
    }
  }

Using this method, the same bot could easily handle several chat rooms at once, including requests to grab data from the internet (such as torrents, Wikipedia summaries, definitions, etc.) using screen scraping or APIs. It would also be trivial to write a simple web server using this model, just treat each HTTP request as a separate task to be completed by an actor. In fact, using a very simple web server with a file cache, I was able to easily handle over 3000 conn/s with under 150 lines of code. Try it out, I think you might end up enjoying it. It is a very fun model to program with.

Post to Twitter Post to Digg Post to Facebook Post to Reddit

This entry was posted in Coding Tips and tagged , , , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>