34

With reference to this answer, I am wondering is this correct?

@synchronized does not make any code "thread-safe"

As I tried to find any documentation or link to support this statement, for no success.

Any comments and/or answers will be appreciated on this.

For better thread safety we can go for other tools, this is known to me.

Community
  • 1
  • 1
Anoop Vaidya
  • 46,283
  • 15
  • 111
  • 140
  • 1
    For what it's worth (and you know my position on this), I believe that if you use `@synchronized` correctly, it ensures thread-safety. As I read it, that answer is saying that if you misuse it (e.g. reference the wrong synchronization token), your code will not be thread-safe. But I think the same can be said of almost any synchronization technique, that if you use it incorrectly, your code will not be thread-safe. I think that lawicko's answer is otherwise quite good, but I think he over-states the case re `@synchronized`. Regardless, there are better ways to ensure thread-safety. – Rob Mar 13 '13 at 18:05
  • I tried here just to see, if some big guns answer with some valid examples, however I may loose some points, but this will be helpful for me and for others. – Anoop Vaidya Mar 13 '13 at 18:07
  • 5
    @synchronize creates locks. It does not create thread-safety. It is one of the tools in your toolbox to attain thread-safety. The reason it is not thread-safe out of the box is that you are still opening yourself up to problems (including dead-locks). There are better ways to ensure thread-safety. What is it that you need it for? Perhaps we can help. – JonathanC Mar 13 '13 at 18:07
  • 1
    Yes, @synchronized isn't going to make your code magically thread safe. Proper use and implementation will can make your code thread safe though (although other methods are often preferable). – DBD Mar 13 '13 at 18:19

6 Answers6

41

@synchronized does make code thread safe if it is used properly.

For example:

Lets say I have a class that accesses a non thread safe database. I don't want to read and write to the database at the same time as this will likely result in a crash.

So lets say I have two methods. storeData: and readData on a singleton class called LocalStore.

- (void)storeData:(NSData *)data
 {
      [self writeDataToDisk:data];
 }

 - (NSData *)readData
 {
     return [self readDataFromDisk];
 }

Now If I were to dispatch each of these methods onto their own thread like so:

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      [[LocalStore sharedStore] storeData:data];
 });
 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      [[LocalStore sharedStore] readData];
 });

Chances are we would get a crash. However if we change our storeData and readData methods to use @synchronized

 - (void)storeData:(NSData *)data
 {
     @synchronized(self) {
       [self writeDataToDisk:data];
     }
 }

 - (NSData *)readData
 { 
     @synchronized(self) {
      return [self readDataFromDisk];
     }
 }

Now this code would be thread safe. It is important to note that if I remove one of the @synchronized statements however the code would no longer be thread safe. Or if I were to synchronize different objects instead of self.

@synchronized creates a mutex lock on the object you are syncrhonizing. So in other words if any code wants to access code in a @synchronized(self) { } block it will have to get in line behind all previous code running within in that same block.

If we were to create different localStore objects, the @synchronized(self) would only lock down each object individually. Does that make sense?

Think of it like this. You have a whole bunch of people waiting in separate lines, each line is numbered 1-10. You can choose what line you want each person to wait in (by synchronizing on a per line basis), or if you don't use @synchronized you can jump straight to the front and skip all the lines. A person in line 1 doesn't have to wait for a person in line 2 to finish, but the person in line 1 does have to wait for everyone in front of them in their line to finish.

Jack Freeman
  • 1,414
  • 11
  • 18
  • 4
    This is all good information and correct, however, I'd only add that using @synchronized or traditional locks in general is no longer Apple's recommended strategy for thread-safety. As contention increases, queueing operations is far more efficient. – isaac Mar 13 '13 at 18:12
  • 2
    Yeah, I mean ideally from my example you would have a serial queue to do all of your database operations on. But obviously I wanted to show where @synchronized can come in handy. Especially if you don't know what thread people will be calling your class from. – Jack Freeman Mar 13 '13 at 18:18
22

I think the essence of the question is:

is the proper use of synchronize able to solve any thread-safe problem?

Technically yes, but in practice it's advisable to learn and use other tools.


I'll answer without assuming previous knowledge.

Correct code is code that conforms to its specification. A good specification defines

  • invariants constraining the state,
  • preconditions and postconditions describing the effects of the operations.

Thread-safe code is code that remains correct when executed by multiple threads. Thus,

  • No sequence of operations can violate the specification.1
  • Invariants and conditions will hold during multithread execution without requiring additional synchronization by the client2.

The high level takeaway point is: thread-safe requires that the specification holds true during multithread execution. To actually code this, we have to do just one thing: regulate the access to mutable shared state3. And there are three ways to do it:

  • Prevent the access.
  • Make the state immutable.
  • Synchronize the access.

The first two are simple. The third one requires preventing the following thread-safety problems:

  • liveness
    • deadlock: two threads block permanently waiting for each other to release a needed resource.
    • livelock: a thread is busy working but it's unable to make any progress.
    • starvation: a thread is perpetually denied access to resources it needs in order to make progress.
  • safe publication: both the reference and the state of the published object must be made visible to other threads at the same time.
  • race conditions A race condition is a defect where the output is dependent on the timing of uncontrollable events. In other words, a race condition happens when getting the right answer relies on lucky timing. Any compound operation can suffer a race condition, example: “check-then-act”, “put-if-absent”. An example problem would be if (counter) counter--;, and one of several solutions would be @synchronize(self){ if (counter) counter--;}.

To solve these problems we use tools like @synchronize, volatile, memory barriers, atomic operations, specific locks, queues, and synchronizers (semaphores, barriers).

And going back to the question:

is the proper use of @synchronize able to solve any thread-safe problem?

Technically yes, because any tool mentioned above can be emulated with @synchronize. But it would result in poor performance and increase the chance of liveness related problems. Instead, you need to use the appropriate tool for each situation. Example:

counter++;                       // wrong, compound operation (fetch,++,set)
@synchronize(self){ counter++; } // correct but slow, thread contention
OSAtomicIncrement32(&count);     // correct and fast, lockless atomic hw op

In the case of the linked question you could indeed use @synchronize, or a GCD read-write lock, or create a collection with lock stripping, or whatever the situation calls for. The right answer depend on the usage pattern. Any way you do it, you should document in your class what thread-safe guarantees are you offering.


1 That is, see the object on an invalid state or violate the pre/post conditions.

2 For example, if thread A iterates a collection X, and thread B removes an element, execution crashes. This is non thread-safe because the client will have to synchronize on the intrinsic lock of X (synchronize(X)) to have exclusive access. However, if the iterator returns a copy of the collection, the collection becomes thread-safe.

3 Immutable shared state, or mutable non shared objects are always thread-safe.

Jano
  • 62,815
  • 21
  • 164
  • 192
12

Generally, @synchronized guarantees thread safety, but only when used correctly. It is also safe to acquire the lock recursively, albeit with limitations I detail in my answer here.

There are several common ways to use @synchronized wrong. These are the most common:

Using @synchronized to ensure atomic object creation.

- (NSObject *)foo {
    @synchronized(_foo) {
        if (!_foo) {
            _foo = [[NSObject alloc] init];
        }
        return _foo;
    }
}

Because _foo will be nil when the lock is first acquired, no locking will occur and multiple threads can potentially create their own _foo before the first completes.

Using @synchronized to lock on a new object each time.

- (void)foo {
    @synchronized([[NSObject alloc] init]) {
        [self bar];
    }
}

I've seen this code quite a bit, as well as the C# equivalent lock(new object()) {..}. Since it attempts to lock on a new object each time, it will always be allowed into the critical section of code. This is not some kind of code magic. It does absolutely nothing to ensure thread safety.

Lastly, locking on self.

- (void)foo {
    @synchronized(self) {
        [self bar];
    }
}

While not by itself a problem, if your code uses any external code or is itself a library, it can be an issue. While internally the object is known as self, it externally has a variable name. If the external code calls @synchronized(_yourObject) {...} and you call @synchronized(self) {...}, you may find yourself in deadlock. It is best to create an internal object to lock upon that is not exposed outside of your object. Adding _lockObject = [[NSObject alloc] init]; inside your init function is cheap, easy, and safe.

EDIT:

I still get asked questions about this post, so here is an example of why it is a bad idea to use @synchronized(self) in practice.

@interface Foo : NSObject
- (void)doSomething;
@end

@implementation Foo
- (void)doSomething {
    sleep(1);
    @synchronized(self) {
        NSLog(@"Critical Section.");
    }
}

// Elsewhere in your code
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
Foo *foo = [[Foo alloc] init];
NSObject *lock = [[NSObject alloc] init];

dispatch_async(queue, ^{
    for (int i=0; i<100; i++) {
        @synchronized(lock) {
            [foo doSomething];
        }
        NSLog(@"Background pass %d complete.", i);
    }
});

for (int i=0; i<100; i++) {
    @synchronized(foo) {
        @synchronized(lock) {
            [foo doSomething];
        }
    }
    NSLog(@"Foreground pass %d complete.", i);
}

It should be obvious to see why this happens. Locking on foo and lock are called in different orders on the foreground VS background threads. It's easy to say that this is bad practice, but if Foo is a library, the user is unlikely to know that the code contains a lock.

Community
  • 1
  • 1
Holly
  • 5,270
  • 1
  • 24
  • 27
  • this _lockObject = [[NSObject alloc] init] inside the init function. Is that literally all you need to do? Or do you then need to use the _lockObject in each method you want locked? – jdog Mar 18 '14 at 13:57
  • 1
    Can you explain that deadlock again? Isn't deadlock always caused by Thread 1 doing: Lock(A); Lock (B); and Thread 2 doing: Lock(B); Lock(A). What does @synchronized (self) have to do with that? – kevlar Jun 13 '14 at 21:37
  • is the `[self bar]` code inside the block also synchronized? Example, the that method calls 30 more methods in sub calls, are they all considered to be within the critical section? – Just a coder Oct 03 '15 at 10:20
  • The lock is held the entire time that code in the section is executing. – Holly Oct 03 '15 at 13:22
  • I agree with Jay. What does `@synchronized(self)` have to do with deadlock? `@synchronized` uses recursive locks. If some external code uses `@synchronized` on your `self` object, how is that in any way a problem? Could you provide an actual code example where deadlock is caused? Thanks! – Todd Lehman Mar 30 '16 at 00:07
  • How to break the deadlock in your last example, I used _lockObject inside Foo's init function but the result is the same. – gabbler Dec 03 '17 at 13:35
  • @gabbler The first example is what *NOT* to do. Exactly what did you do? – Holly Dec 03 '17 at 14:48
  • @Holly, I just changed `doSomething` method, change `@synchronized(self)` to `@synchronized(_lockObject)`, the rest is the same, it still deadlocks. – gabbler Dec 04 '17 at 00:17
  • What is it deadlocking with? What else is holding the lock? – Holly Dec 04 '17 at 00:36
4

@synchronized alone doesn't make code thread safe but it is one of the tools used in writing thread safe code.

With multi-threaded programs, it's often the case of a complex structure that you want to be maintained in a consistent state and you want only one thread to have access at a time. The common pattern is to use a mutex to protect a critical section of code where the structure is accessed and/or modified.

progrmr
  • 75,956
  • 16
  • 112
  • 147
3

@synchronized is thread safe mechanism. Piece of code written inside this function becomes the part of critical section, to which only one thread can execute at a time.

@synchronize applies the lock implicitly whereas NSLock applies it explicitly.

It only assures the thread safety, not guarantees that. What I mean is you hire an expert driver for you car, still it doesn't guarantees car wont meet an accident. However probability remains the slightest.

It's companion in GCD(grand central dispatch) is dispatch_once. dispatch_once does the same work as to @synchronized.

  • 2
    Its a bit late answer, however the Driver example rocks :) +1 – Anoop Vaidya Jan 08 '15 at 09:22
  • "Assures" and "guarantees" mean exactly the same thing. I think you meant to say: "It is only a tool to help you to write thread-safe code, but it does not guarantee thread safety." – Todd Lehman Mar 30 '16 at 00:11
  • `dispatch_once` certainly DOES NOT do the same thing as `@synchronized`. `dispatch_once` executes code ONCE and ONCE only, hence the name. – Mel May 09 '19 at 23:04
1

The @synchronized directive is a convenient way to create mutex locks on the fly in Objective-C code.

side-effects of mutex locks:

  1. deadlocks
  2. starvation

Thread safety will depend on usage of @synchronized block.

Parag Bafna
  • 22,812
  • 8
  • 71
  • 144
  • Thanks for the answer, And I know this question will help many of us, as in short most of us were knowing @sync makes threadsafe isn't it ? :) – Anoop Vaidya Mar 14 '13 at 07:08