Showing posts with label Concurrent-Programming. Show all posts
Showing posts with label Concurrent-Programming. Show all posts

Tuesday, February 03, 2009

Actor concurrency model in a nutshell

While the necessity of writing software applications capable of exploiting the multi-processor architecture of today computers is more and more common, concurrent programming is often perceived as an hard task.
No wonder, so, if many languages come to our rescue by supporting concurrent programming through first-class syntax support, or through higher level user libraries.
Two well-known languages providing explicit concurrent programming support are Erlang and Scala, and both have in common the same concurrency model: actors.

The actor model is a concurrency abstraction based on the concepts of message-passing concurrency: very different from the shared state concurrency model we're used to in general purpose languages such as Java, but more efficient and easier to program with.

Let's see the difference between the two.

Shared-state concurrency is based on two fundamental concepts: resource sharing and resource synchronization.
As already said, it's the most common scenario with general purpose OO languages such as Java: it's composed by computational units (often implemented as threads) concurrently executing code sections containing resources that must be shared, and hence, synchronized in order to guarantee correct ordering, visibility and data consistency.

Message-passing concurrency, also known as share-nothing concurrency, is the exact opposite: here, computational units are just endpoints exchanging immutable messages one another, and reacting to received messages by executing a given computation.
In such a concurrency model, so, there isn't any shared resource, nor there is any need for resource synchronization: messages are immutable, and each computational unit is only able to change its own state in response to a received message.
It has several interesting consequences, making message-passing concurrency preferable over shared-state one:

  • Message-passing concurrency is safer: there are no shared resources, so there is no possibility to corrupt data due to concurrent access.

  • Message-passing concurrency is faster: there is no resource synchronization, so there are no bottlenecks, deadlocks, livelocks, or similar locking issues.

  • Message-passing concurrency is easier: once you get used to the new paradigm, not to have to think at how to share and synchronize resources, is a big relief.


The actor concurrency model is a form of message-passing concurrency, based on:

  • Actors: the computational units capable of sending messages and reacting to received ones by executing a given function.

  • Messages: the form of communication used by actors in order to exchange data and carry on some kind of computation based on that data.

  • Mailboxes (or channels): a kind of buffer every actor has for storing received messages which haven't been processed yet.


Every actor can communicate with other actors by obtaining their mailbox (or channel) "address", and then sending a message to it: this is the only way an actor has for changing the state of the system.
Every actor can receive messages and process them by executing a behavior function: such a function can only change the state of the actor itself, or send new messages to other actors (already existent or created on-the-fly).
Communication between actors is completely asynchronous and decoupled: that is, actors do not block waiting for responses to their messages; they just send messages and forget, reacting to incoming messages without any correlation to previously sent messages.
A concurrent system implemented trough the actor concurrency model is considered to be:

  • Parallel: several actors can process several messages in parallel, each one independently from the other.

  • Scalable: actors can be implemented in several ways, as local computational units or distributed ones, so
    they can easily scale out to the number of available processors or computers.

  • Reconfigurable: actors can be dynamically added and removed from the system, and then communicate the new topology through special purpose messages.


Obtaining such a properties through a shared-state implementation is harder and requires a lot of challenges.
The actor model provides instead a well-defined, clearly stated way to easily build highly concurrent systems, at a cost of a paradigm shift.

Next time we will see how to implement an actor-based concurrent application through our favorite OO language: Java!

Saturday, December 08, 2007

Owner Based Locking explained

If you attended my presentation at the Rome JavaDay about my real world experience in clustering Atlassian Jira, or if you took a look at my slides, you may already know that one of the challenges was the rewriting of the Jira caching system.
The hardest part of this challenge was to define the cache locking strategy.
That was because of two requirements, due to the Jira code and the way it has been clustered:

  • The need to associate more caches under the same lock.

  • The need to execute arbitrary code blocks atomically: that is, again, under the same lock.


So here it comes my idea of the owner based locking.
It's very simple and I'll explain it in a moment: others may find it useful, or elaborate on that for solving their own locking problems, or just provide useful suggestions.

Let's start with the concept of cache group. The cache group is the entry point for your caching system, where you create caches, put things and get back them and alike.

First step : when you create a cache into the group, assign it to an owner.

The owner is a string, and nothing more. It just serves two purposes: it's the name which associates different caches, establishing some kind of caches sub-group, and their lock.

Here it comes the second step : use the owner as a lock for different caches sub-groups.

This is a lock striping technique.
Lock striping techniques prescribe you to distribute your data in several "stripes" and split your big, fat, lock in several locks, assigning each one to a different stripe : this is done for enhancing thread concurrency, given that different threads will be able to concurrently access different parts (stripes) of your data because guarded by different locks.
In our caching system, the stripes are the different caches sub-groups, and the locks are the owners.
A code snippet will help clarifying.
Here is how a value is retrieved from a cache into the cache group:

public Object get(String cacheName, Object key) {
CacheHolder holder = (CacheHolder) this.group.get(cacheName);
String owner = (String) holder.getOwner();
synchronized (owner) {
Map cache = holder.getCache();
return cache.get(key);
}
}

As you can see, the synchronized variable is the owner string: by doing so, caches with the same owner will be protected by the same lock, while caches with different owners will be accessed concurrently, but without compromising the whole thread safety.

Finally : use callbacks for atomically executing code blocks.

This is best explained by first showing the callback interface:

public interface AtomicContext {

public Object execute();
}

And how it's used into the cache group:

public Object executeAtomically(AtomicContext context, String owner) {
synchronized (owner) {
return context.execute();
}
}

As you can see, the execute() method is executed atomically under the given owner lock.
However, here I have to raise a warning flag: if the implementation of the execute() method uses caches belonging to other owners, it may cause deadlocks; so take care and use only caches whose owner is the same as the one provided to the executeAtomically() method!

That's the owner based locking.
You can take the full source code of the cache group used for clustering Atlassian Jira from the Scarlet Jira extension distribution.

Any feedback will be highly appreciated.

Cheers!