iOS tips: @synchronized

Post by Steve Vlaminck

If you’re just getting started with threading in Objective-C, it won’t be long before you’ll need to make some thread-safe modifications to objects. One of the many useful tools that Objective-C gives us is the @synchronized directive. From the documentation:

The @synchronized directive is a convenient way to create mutex locks on the fly in Objective-C code. The @synchronized directive does what any other mutex lock would do—it prevents different threads from acquiring the same lock at the same time.

Using @synchronized is super easy

@synchronized(key) {
// thread-safe code goes here
}

Examples!

First we’re going to create an NSMutableArray and fill it with garbage.

bigArray = [NSMutableArray arrayWithCapacity:5];
 for (int i = 0; i < 5; i++) {
  [bigArray addObject:[NSString stringWithFormat:@"object-%i", i]];
}

And a method to display/modify the array.

- (void)updateBigArray:(NSString *)value {
 for (int j = 0; j < bigArray.count; j++) {
  NSString *currentObject = [bigArray objectAtIndex:j];

  [bigArray replaceObjectAtIndex:j withObject:[currentObject stringByAppendingFormat:@"-%@", value]];

  NSLog(@"%@", [bigArray objectAtIndex:j]);
 }
}

Now let’s call our method on two separate threads. Notice I’m sending foo in for both of them. I’ll explain why in a bit.

NSString *foo = @"foo";

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
 [self updateBigArray:foo];
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
 [self updateBigArray:foo];
});

This gives us the output:

object-0-foo
object-0-foo
object-1-foo-foo
object-1-foo
object-2-foo
object-2-foo
object-3-foo
object-3-foo
object-4-foo-foo
object-4-foo

Both threads are working over the top of each other which could lead to app crashes depending on what you’re doing. So let’s update our method to use @synchronized with value as our lock key.

- (void)updateBigArray:(NSString *)value {
 @synchronized (value) {
  for (int j = 0; j < bigArray.count; j++) {
   NSString *currentObject = [bigArray objectAtIndex:j];

   [bigArray replaceObjectAtIndex:j withObject:[currentObject stringByAppendingFormat:@"-%@", value]];

   NSLog(@"%@", [bigArray objectAtIndex:j]);
  }
 }
}
object-0-foo
object-1-foo
object-2-foo
object-3-foo
object-4-foo
object-0-foo-foo
object-1-foo-foo
object-2-foo-foo
object-3-foo-foo
object-4-foo-foo

That looks a lot better. What’s happening is that our @synchronized block effectively pauses one thread, allowing the other thread access to the block, then un-pauses it once the first has finished.

So what about the key?

This is where it gets interesting. If you pass the same object to @synchronized as the key, it will get paused (which we just saw). However, if you send a different object than the key it will get through. Remember when we passed foo through twice? Let’s pass something else the second time instead.

NSString *foo = @"foo";
NSString *bar = @"bar";

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
 [self updateBigArray:foo];
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
 [self updateBigArray:bar];
});
object-0-foo
object-0-bar
object-1-foo
object-1-bar
object-2-foo
object-2-foo-bar
object-3-foo
object-3-foo-bar
object-4-foo
object-4-foo-bar

There could be reasons why you would want to lock on value, but in this case it would be smarter to use a different key.
Let’s use

 @synchronized (bigArray) {
...

instead of

 @synchronized (value) {
...

object-0-foo
object-1-foo
object-2-foo
object-3-foo
object-4-foo
object-0-foo-bar
object-1-foo-bar
object-2-foo-bar
object-3-foo-bar
object-4-foo-bar

In the end

@synchronized is a great shorthand mutex lock. It may not be the most efficient lock, but chances are you won’t notice. If you’re just getting started with threading, I highly recommend getting more familiar with it.

 

This entry was posted in Software Development and tagged , , , , , . Bookmark the permalink.

Related Posts:

5 Responses to iOS tips: @synchronized

  1. José Netto says:

    Thanks for the post! Simple and great info towards threads!

  2. Dmitry says:

    Interesting fact about how synchronize is implemented is that mutex is dynamically assigned to an object. Every next synchronize will get the same mutex that first one assigned. It means that you can synchronize on a random integer:

    Thread1:
    @synchronize((id)0×12345) {
    }

    Thread2:
    @synchronize((id)0×12345) {
    }

    will work as expected, i.e. Thread2 will wait until Thread1 finishes its work (or other way around).

  3. Pingback: Last weeks iOS resources (Oct 21 2012) | E.V.I.C.T. B.V.

  4. Pingback: Tutorial on @synchronized for thread safe code « [iOS developer:tips];

  5. Pingback: Can Ürek | Tutorial on @synchronized for thread safe code

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>