Objective-C: Atomic, properties, threading and/or custom setter/getter

Jim Correia asked on cocoa-dev:

Can you offer any advice for the times when I must write my own accessor, but want it to have similar behaviors to the autogenerated accessor? In particular, I’m thinking about the default atomic behavior. Is this simply wrapping the work in a @synchronized(self) block? Something else?

First, some context. Jim is referring to Objective-C 2.0′s properties and, more specifically, to the behavior of methods automatically synthesized by the compiler.

By default, synthesized accessors are atomic. Atomic does not mean thread safe.

Atomic means that your code can pound on the setter and/or getter from multiple threads without fear of a crash or inconsistent value. For objects in non-GC, it means that the getter will never return an unreleased value. For structures, it means that you’ll never see an NSRect where the origin is some old value and the size is some new value.

As that implies, atomic requires that both the setter and getter are synchronized using some kind of locking mechanism. This can be as simple as (and as Jim suggests) doing something like this:

- (id) foo {
	@synchronized(self) {
		return [[foo retain] autorelease];
	}
}
- (void) setFoo: (id) aFoo
{
	@synchronized(self) {
		if (aFoo != foo) {
			[aFoo retain];
			[foo release];
			foo = aFoo;
		}
	}
}

Of course, this isn’t the full picture. You would also need to make sure you go through the setter/getter throughout the implementation of your class to ensure that the atomic behavior is preserved.

And this is grossly ineffecient. The @synchronized(self) will cause all methods synchronized in a similar fashion to block on each other. If you are particularly creative, you could likely even create a deadlock through such blanket use of @synchronized(self).

The alternative is to @synchronize() on some other primitive. Maybe a static NSString or something per attribute.

This entire discussion ignores all issues related to exception handling. One exception tossed in exactly the right spot and deadlocks will ensue. (As an aside, this is also a good demonstration of why “kill that thread over there” is not an obviously available mechanism — there is no way to know what state said thread may be in.)

Internally, synthesized methods use a complex mix of stuff to try to maximize effeciency and minimize risk of deadlock.

Ugly, huh?

And largely totally pointless.

Atomic is not synonymous with thread safe. Specifically, the threading capabilities of an API are largely defined by how said API can be used while keeping the underlying object graph and data self-consistent.

Or, more importantly, ensuring that the windows of inconsistency are tightly controlled.

Atomic doesn’t really help. Take this very simple example (64-bit — synthesized ivars, too!):

@interface Dude:NSObject
@property(readwrite, copy) NSString *firstName;
@property(readwrite, copy) NSString *lastName;
- (NSString *) fullName;
@end

@implementation Dude
@synthesize firstName, lastName;
- (NSString *) fullName {
	return [NSString stringWithFormat: @"%@ %@", firstName, lastName];
@end

No amount of atomicity is going to make this API thread safe. In this case, “thread safety” is more about “transactional safety”. To achieve that will require discipline in the the rest of the code.

For example, if you are changing both the firstName and the lastName, fullName can’t be called in between without yielding a bogus value. Yet, sometimes, you might only be changing the firstName (parents named Dude “Sue”) or the lastName (marriage or divorce).

So, in this case, atomicity adds a lot of overhead to achieve a bit more stability in situations that will return bogus state.

And this is a dead simple example. Imagine the same kinds of situations, but spanning a more complex object graph.

To reiterate: Atomic does not mean thread safe.

While atomic accessors play a role, it is minor.

In general, efficient threading is typically achieved by isolating subgraphs of objects to a single thread. Within that subgraph, there is no need for synchronization primitives, but breaking the “single thread” rule — through bug or hack — will often be fatal (and not something atomic will really help with).

Creating a thread capable API is hard. I would suggest that you refer to the CoreData Multi-Threading documentation as it is both thorough and gives some indication as to the depth of issues that must be considered to achieve success.



5 Responses to “Objective-C: Atomic, properties, threading and/or custom setter/getter”

  1. Benjamin Stiglitz says:

    While they don’t ensure consistency, atomic properties do ensure that individual properties will have valid values; that is, that there will be no partially written values when read across threads.

  2. David Leppik says:

    Well, if you want to define things that way, no multithreaded code of any complexity is thread safe. Typically, when one says that something is thread safe, it’s actually less restrictive than being atomic. Thread safe means that code works as intended when called simultaneously from multiple threads. When you’re dealing with multithreaded code, you need to spell out your intentions very carefully.

    Your name code is a perfect example. You can make the fullName method atomic, but that won’t help when someone needs a fullNameWithLastNameFirst method, or any of a zillion other variations.

    I’ve been writing multithreaded code in Java for over a decade, and I firmly believe that 99% of the time accessors should not be thread safe. It just gives a misplaced sense of security.

    For the Dude class, I’d make it explicitly thread unsafe, so that people using it won’t be tempted to use the same Dude in multiple threads. If you need to access it from multiple threads, you make copies of it. If that’s too inefficient for you, then you make its fields read-only.

    They say that multithreaded programming is hard. That’s half true. It’s nigh impossible to debug when things go wrong. (Especially when the processor is allowed to execute code out-of-order in each thread.) But the trick to writing multithreaded programs is not to do anything clever. Simple, blunt rules (e.g. copy, don’t share) get the job done every time. And, thanks to the machine second-guessing you, they usually turn out to be more efficient than cleverness anyway.

  3. Travis Cripps says:

    Nice post, Bill. I appreciate your clear examples and explanation of the difference between atomic and thread safe. Thanks.

  4. Propertys for accessors — Daubit-Blog says:

    [...] Atomicity assure safe access of multiple threads. Only one thread is allowed to access the accessors at the same time.To avoid this the attribute „nonatomic“ has to be named. More at friday.com [...]

  5. jayray says:

    Nicely explained, Bill.

Leave a Reply

Line and paragraph breaks automatic.
XHTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>