Panther adds a new Objective-C directive, @synchronized(). See http://developer.apple.com/documentation/Cocoa/Conceptual/General/ObjectiveC/General/LanguageOverview/chapter_4_section_9.html#//apple_ref/doc/uid/20001424-BCIFAFAI
The @synchronized() directive seems to work like Java’s. It takes any General/ObjectiveC object, which acts as a mutex semaphore.
Apple’s example:
Account *account = [Account accountFromString:[accountField stringValue]]; double deposit_amount = [amountField doubleValue]; double balance;
// Get the semaphore. id accountSemaphore = [Account semaphore];
@synchronized(accountSemaphore) { // Critical code. balance = [account balance] + deposit_amount; [account setBalance:balance]; }
[balanceField setDoubleValue:balance]
…must…resist…urge…to…scream… ::General/KritTer vents his anger at this irksome copying of one of the less sanitary sides of Java by scrunching his face and squinting oddly::
Okay, back now. It seems Apple have merely made it easier and simpler to add an General/NSRecursiveLock to an object, and to use it. That’s not so bad, I guess. Provided people don’t assume that because it’s easier to do, it must be easy to do. (mutter mutter race conditions mutter mutter deadlocks mutter mutter…) – General/KritTer
What I’m worried about is: you can use any object as the mutex. Does this mean all objects have a built-in General/NSRecursiveLock as in Java? �General/DustinVoss
In the free ADC TV session he briefly mentioned the synchronize keyword and said, that unlike Java, they did not have a mutex pr. object – but this just left me puzzled… maybe I didn’t hear it correctly…
Does this result in any performance benefit over using General/NSLocks? –General/OwenAnderson
No performance benefit, but cleaner synchronization in code that uses General/ObjC exceptions. In my tests on 10.3.4, a General/NSLock is 4 times faster than @synchronized while a General/NSRecursiveLock is 2 times faster than @synchronized. - General/SynapticPulse
Implementation details: see <objc/objc-sync.h>. In particular, the comment for objc_sync_enter()
says “Allocates recursive pthread_mutex
associated with ‘obj’ if needed.” Presumably the mutex is deallocated on objc_sync_exit()
if its count is 0, or on -dealloc
… either of which is horribly ugly. On the other hand, I assume it does the Right Thing with regards to @try
blocks. – General/JensAyton
This implementation doesn’t seem ugly to me. Are you are presuming the pthread_mutex is deallocated after every use, the mutex could very well be deallocated only when the semaphore is deallocated (in General/[NSObject dealloc]), or reused. (See source linked below.)
Also in my testing, it seems that if you “return” or “goto” outside the block while inside a @synchronized block the lock is relinquished (a good thing). This makes managing large blocks of synchronized code easy if you do any of this, without worrying about your locks. ~ General/TimothyHatcher
For implementation details see: http://darwinsource.opendarwin.org/10.3.8/objc4-237/runtime/objc-sync.m
From the GCC source this directive turns into:
{
id _eval_once = <sync semaphore>;
@try {
objc_sync_enter( _eval_once );
/* sync code goes here */
}
@finally {
objc_sync_exit( _eval_once );
} }
One limitation I recently ran into was with nested synchronized directives. You can’t nest them, even if you are locking 2 different objects.
@synchronized( obj1 ) {
@synchronized( obj2 ) {
/* code */
} }
The lock on obj1 is never released.
~ General/TimothyHatcher
Nice catch. Did you file a bug?
“The lock on obj1 is never released”. Do you mean the lock remains locked, or the memory associated with it is not released ? I’m using nested synchronized() calls (on different objects) and the app doesn’t deadlock. Did you notice the same problem in recent versions of the OS ? (ie 10.4.3 ?)
~ Nicolas
See also General/NSLockVsSynchronized