@property
directive and last week we saw how to use the @synthesize
directive to tell the compiler to generate getters and setters for properties. We've covered the most commonly used attributes of the @property
directive: readonly
/readwrite
and assign
/retain
/copy
. Today we'll look at the nonatomic
attribute and talk about what it's for and why you should (or should not) use it.Threads and data
By default, your app's code executes on the main thread of your app's process, along with most Cocoa Touch framework code. Any particular method or function runs uninterrupted from start to finish and as long as that method or function leaves all the data it touches in a good state when it returns, your program runs correctly.
When you have multiple threads in your application, things aren't so easy. One key challenge when using multiple threads is to make sure you only read data when it's in a consistent state. This is similar in concept to using a transaction in a SQL database.
Suppose you have a custom UI object that's defined like this:
@interface MyWidget { CGPoint center; // ... } @property CGPoint center; // ... @end @implementation @synthesize center; // ... @end
If you treat instances of this class as read only when you share them between threads, you're safe. The trouble appears when one or both threads start to make changes to the object. If we were to write the getter and setter for
center
, it would look like this:// example assign-type getter and setter - (CGPoint) center { return center; } - (void)setCenter:(CGPoint)theCenter { center = theCenter; }This looks simple enough, but the compiler is helping us out here. The
center
instance variable is a struct
that's defined like this:// struct CGPoint struct CGPoint { CGFloat x; CGFloat y; };The
setCenter:
method is actually doing something like this:- (void)setCenter:(CGPoint)theCenter { center.x = theCenter.x; center.y = theCenter.y; }Let's look at what happens when one thread calls the setter and a second thread calls the getter. In the simple case, the setter and getter calls don't overlap:
// given MyWidget instance myWidget: // thread 1 calls setter: [myWidget setCenter:CGPointMake(1.0f, 2.0f)]; // setCenter method executes: - (void)setCenter:(CGPoint)theCenter { center.x = theCenter.x; // 1.0f center.y = theCenter.y; // 2.0f } // center is now {1.0f, 2.0f} // ... thread 1 preempted by thread 2 ... // thread 2 calls getter: CGPoint point = [myWidget center]; // center method executes: - (CGPoint) center { return center; // 1.0f, 2.0f } // point is now {1.0f, 2.0f}In this case, we get the answer we expect. Now suppose we do this again, only thread 1 gets preempted by thread 2 in the middle of the
setCenter
method:// myWidget.center is {1.0f, 2.0f} // thread 1 calls setter: [myWidget setCenter:CGPointMake(3.0f, 5.0f)]; // setCenter method executes: - (void)setCenter:(CGPoint)theCenter { center.x = theCenter.x; // 3.0f // ... thread 1 preempted by thread 2 ... // thread 2 calls getter: CGPoint point = [myWidget center]; // center method executes: - (CGPoint) center { return center; // 3.0f, 2.0f } // point is now {3.0f, 2.0f} center.y = theCenter.y; // 5.0f } // myWidget.center is now {3.0f, 5.0f} // but thread 2 read {3.0f, 2.0f}Now thread 2 is working off of a corrupt value and things are likely to go haywire. To solve this problem, we need to prevent all threads from reading
center
until the setCenter:
method is finished. Because this is a common problem in multithreaded code, Objective-C has a special directive to accomplish this: @synchronized
. We can rewrite our getter and setter for the center
property like this:// adding @synchronized to getter and setter - (CGPoint) center { CGPoint theCenter; @synchronized(self) { theCenter = center; } return theCenter; } - (void)setCenter:(CGPoint)theCenter { @synchronized(self) { center = theCenter; } }Now when we read and write
center
from two threads, @synchronized
causes other threads to pause whenever one thread is inside either of the @synchronized
blocks:// myWidget.center is {1.0f, 2.0f} // thread 1 calls setter: [myWidget setCenter:CGPointMake(3.0f, 5.0f)]; // setCenter method executes: - (void)setCenter:(CGPoint)theCenter { @synchronized(self) { center.x = theCenter.x; // 3.0f // ... thread 1 preempted by thread 2 ... // thread 2 calls getter: CGPoint point = [myWidget center]; // center method executes: - (CGPoint) center { CGPoint theCenter; @synchronized(self) { // thread 1 is already synchronized on // self so thread 2 pauses here // ... thread 2 yields and thread 1 runs again ... // still inside @synchronized on thread 1 center.y = theCenter.y; // 5.0f } } // ... thread 1 preempted by thread 2 again ... @synchronized(self) { // now thread 2 resumes theCenter = center; // 3.0f, 5.0f } return theCenter; // 3.0f, 5.0f } // point is now {3.0f, 5.0f}I'm glossing over many of the details of
@synchronized
here. If you're writing multithreaded code, you should read the Threading Programming Guide. The key concept here is that by default, the @synthesize
directive generates this type of synchronization for you.Atomic or
nonatomic
Behind the scenes, the
@synchronized
directive uses a lock to prevent two threads from accessing a @synchronized
block simultaneously. Although acquiring and releasing the lock is very quick, it's not free. Occasionally you have a property that is so frequently accessed that all this locking and unlocking adds up to a noticeable penalty. In these rare cases, you can declare the property to be nonatomic
:@interface MyWidget { CGPoint center; // ... } @property (nonatomic) CGPoint center; // ... @endThe compiler omits the synchronization code when generating
nonatomic
getters and setters. Note that there isn't a corresponding atomic
attribute for @property
; generated getters and setters are synchronized by default.Don't prematurely optimize
Acquiring a lock is very fast in the common case where no other thread is holding it. According to Apple's docs, it takes about 0.0000002 seconds (that's 0.2 microseconds) on a modern Mac. Even though the iPhone is much slower, you need to be acquiring locks hundreds of thousands of times before you should consider synchronization overhead as anything significant. For the vast majority of code, you should simply not even worry about
nonatomic
.Also, keep in mind that the attributes you set on your
@property
declarations only apply when you use @synthesize
to have the compiler generate the getter and setter methods. If you write the getter or setter yourself, the attributes are ignored. Next week we'll look a little more at synchronization and show you how to write a thread safe getter when returning an Objective-C object.
4 comments:
Thanks for the concise explanation!
Let's see if I've got this right:
@property (retain) ... // thread-safe, but vaguely slower
@property (nonatomic, retain) ... // not thread-safe
If so, then your recommendation to leave out nonatomic seems like the more robust default when coding. Any idea, then, why Apple's own samples almost universally use nonatomic?
Because programmers love to micro-optimize, and to prematurely optimize. I often find myself falling into the same trap. It's easy to get caught up in the small details of the code, or worry about the performance of some rarely used function or method. I've got "nonatomic" scattered all over @property declarations in my code too. It wasn't until I sat down to write this blog entry that I actually thought it through and realized that it's just unnecessary clutter, except in rare cases.
Just curious why you put self in @synchronized block not the ivar (like self->center for example)?
You could do locking at a finer granularity, such as per instance variable, but then you need to make sure to synchronize every block of code in your class that uses that ivar. For instance, if you had firstName and lastName ivars, you would need to synchronize on both firstName and lastName inside a method called -fullName that generates the full name on the fly. If you had a -setFullName: method that synchronized on lastName then firstName, calling it concurrently with -fullName could cause a deadlock. The more instance variables and operations your class has, the more likely you'll get something like this wrong. In general it's easier just to synchronize on self, and in most use cases just as efficient.
Post a Comment