Monday, November 05, 2007

A better way to do concurrent programming (Part 1)

A while ago (I think round 2005), some guys from Microsoft Research published a paper about Composable Memory Transactions.

After reading it, I began doing a small implementation in Java (quite ugly, btw), and I was astonished by the possibilities. So quite naturally (being the geek I am), I started toying with the idea of implementing a set of extensions for Java.

In this and other posts, I'll try to make an introduction to the basic idea. Note that I will probably change existing posts quite often to correct mistakes or ommissions. In this post I'll focus on an overview of the basic idea, in a future post I'll address composition of transactions (the C in CMT), static checking for side effects, and alternatives to implement the runtime part of this. If you're really curious, I recommend reading the original paper.

The gist of the idea is to replace common locking based semantics with in-memory transactions, so instead of writing complex locking semantics, you rely on optimistic locking handled by the runtime.

The following example shows how a producer-consumer problem can be solved with a queue of fixed size using Java with CMT:

public class BufferedQueue {
private LinkedList list;
public BufferedQueue() {
list = new LinkedList();
}
public Object consume() {
Object value;
atomic {
if(list.isEmpty()) {
retry;
}
value = list.removeFirst();
}
return value;
}
public void produce(Object value) {
atomic {
if(list.size() > 10) {
retry;
}
list.add(value);
}
}
public Object peek() {
Object value;
atomic {
if(list.isEmpty()) {
retry;
}
value = list.getFirst();
} else {
value = null;
}
return value;
}
}

Take a look at two keywords: atomic and retry

The atomic keyword demarcates a transaction. That means that this block is all-or-nothing (well get to the else later). For example let's take a closer look at the consume method.

public Object consume() {
Object value;
atomic {
if(list.isEmpty()) {
retry;
}
value = list.removeFirst();
}
return value;
}

The atomic statement starts a transaction. In this transaction we first check to see if the list is empty. If it is, we issue a retry statement.
The retry statement, rolls-back all changes and suspends the transaction until at least one of the fields used in the block (either directly or indirectly) is modified by another transaction committing. When this happens, the execution is restarted in a new transaction at the beginning of the enclosing atomic block.
If this time the list is not empty, the first element is removed from the list, and the transaction is committed. If at the time of the commit there is a conflict with another transaction, the execution is rolled back and retried.

Note that it is very important that the atomic blocks do not have side-effects (such as creating files, etc.) outside of the control of the transaction manager.

(to be continued...)

13949712720901ForOSX - Java for OSX

In a blog post henry resumes the sad state of affairs of Java in OSX.

So here I am, joining this campaign by posting this entry in protest! Apple, please don't let us down!