Which memory model?

In his blog, Eric Eilebrecht explains why when writing multithreaded applications today we should stick to the weak ECMA memory model instead of CLR’s much stronger memory model.

In principal, I have no issue with using a weaker model than the CLR memory model but my main concern is that “at what cost are we prepared to endorse a weaker model?”

Writing correct lock-free or low-lock data structures can be challenging to say the least, even when relying on a strong memory model such as CLR’s. The complexity could increase a few fold when targeting a weaker memory model.

Consciously or more often instinctively we write code based on a set of assumptions (requirements, APIs, memory models and hardware architecture). These assumptions however do change. Not always these assumptions are documented or easily transferable to other developers.

Imagine the following simplified implementation of a lock-free stack:

public class LockFreeStack<T> where T : class

{

  private LinkedListNode<T> _head;

  public void Push(T item)

  {

    var newNode = new LinkedListNode<T>();

    newNode.Item = item;

    do

    {

      newNode.NextNode = _head;

      //_head = newNode;

    } while (!SyncHelper.CompaneAndSwape<LinkedListNode<T>>(

      ref _head, newNode.NextNode, newNode));

  }

  public T Pop()

  {

    LinkedListNode<T> node;

    do

    {

      node = _head;

      if (node == null)

        return default(T);

      //_head = node.NextNode;

    } while (!SyncHelper.CompaneAndSwape<LinkedListNode<T>>(

      ref _head, node, node.NextNode));

    return node.Item;

  }

}

It makes a number of assumptions. For instance, it assumes that you are not interested in knowing the length of the stack. Adding a Count property to this class would require a massive rethink of its lock-free algorithm (this may add a possible second write to a shared memory at the time of Pop and Push). It might even require a full rewrite of the class. Therefore when assumptions change, it is not always trivial to refactor and repurpose the code. Utmost level of care must be taken to ensure that issues and bugs are avoided.

In my experience, it is often simpler to rewrite the code and use as much testing as possible on multiprocessor machines.

The second reason that writing applications for a weaker memory model today is not advisable is because we don’t have a runtime to test our applications. Many concurrency issues and bugs are discovered at the time of testing.