Atomic operations are your friends

2009
02.20

I often come across multithreaded codes that work 99.9% of the time but share a common flaw. They exhibit unrepeatable behavior on occasion because such codes treat assignment as an atomic operation.

This assumption is fundamentally flawed since assignment such as  int64_t a=b; may take 2 or ore CPU instructions to complete. For instance, in the case of a 64-bit integer assignment could execute inside two instructions; one for low- and high-word of the register. The instruction bipole could in theory (and does in practice) get interrupted by other threads. Such bugs are extremely difficult to track and isolate since assignment is indeed atomic for most cardinal datatypes in C/C++ but isn’t required to be. To appreciate the complexity multithreading gives rise to especially when multiple cores/CPUs are involved read this article.

Many of my fellow developers could save themselves from glib frustrations of caffeine charged moonwalks through late-night debugging sessions by being more conscious of non-atomicity of assignment. In fact, an added benefit is that an understanding these facilities often eliminates the need for synchronization primitives in a number of situations consequently making codes faster and easier to follow.

The facilities are a part of Kernel framework and can be conveniently imported in ObjC as follows:

#import <libkern/OSAtomic.h>

I should point out that atomicity of (at least assignment) could also be achieved through the use of ObjC 2.0 properties which are atomic by default. However, the sweet of OSAtomic-operations is reacher and eliminates the need for overzealous property syntax.

There are however a few quirks requiring our attention if we are to make use of the API. OSAtomicXXX calls require their arguments to be aligned at natural boundaries in memory. Specifically, 32-bit variables must be aligned at 4 byte boundaries and 64-bit ones at 8 byte boundaries. The easiest way to insure that such alignment takes place in g++ compiled code is to use a proprietary extension. Also don’t forget to declare them volatile since a compiler may not warn you if you omit volatile from declaration. The following code demonstrates how one may declare instance variables for use in OSAtomicXXX calls.

typedef volatile int64_t vint64;

@interface MyObject : NSObject
{
vint32 shouldContinue __attribute__ ((aligned (4)));
vint32 didFinish __attribute__ ((aligned (4)));
vint64 haveDataToReload __attribute__ ((aligned (8)));
}
@end

Your Reply

You must be logged in to post a comment.