apouche
apouche

Reputation: 9973

is there a way that the synchronized keyword doesn't block the main thread

Imagine you want to do many thing in the background of an iOS application but you code it properly so that you create threads (for example using GCD) do execute this background activity.

Now what if you need at some point to write update a variable but this update can occur or any of the threads you created.

You obviously want to protect that variable and you can use the keyword @synchronized to create the locks for you but here is the catch (extract from the Apple documentation)

The @synchronized() directive locks a section of code for use by a single thread. Other threads are blocked until the thread exits the protected code—that is, when execution continues past the last statement in the @synchronized() block.

So that means if you synchronized an object and two threads are writing it at the same time, even the main thread will block until both threads are done writing their data.

An example of code that will showcase all this:

// Create the background queue
dispatch_queue_t queue = dispatch_queue_create("synchronized_example", NULL);

// Start working in new thread
dispatch_async(queue, ^
               {                   
                   // Synchronized that shared resource
                   @synchronized(sharedResource_)
                   {   
                       // Write things on that resource
                       // If more that one thread access this piece of code:
                       // all threads (even main thread) will block until task is completed.
                       [self writeComplexDataOnLocalFile];
                   }                         
               });

// won’t actually go away until queue is empty
dispatch_release(queue);

So the question is fairly simple: How to overcome this ? How can we securely add a locks on all the threads EXCEPT the main thread which, we know, doesn't need to be blocked in that case ?

EDIT FOR CLARIFICATION

As you some of you commented, it does seem logical (and this was clearly what I thought at first when using synchronized) that only two the threads that are trying to acquire the lock should block until they are both done.

However, tested in a real situation, this doesn't seem to be the case and the main thread seems to also suffer from the lock.

I use this mechanism to log things in separate threads so that the UI is not blocked. But when I do intense logging, the UI (main thread) is clearly highly impacted (scrolling is not as smooth).

So two options here: Either the background tasks are too heavy that even the main thread gets impacted (which I doubt), or the synchronized also blocks the main thread while performing the lock operations (which I'm starting reconsidering).

I'll dig a little further using the Time Profiler.

Upvotes: 2

Views: 3919

Answers (2)

mttrb
mttrb

Reputation: 8345

I believe you are misunderstanding the following sentence that you quote from the Apple documentation:

Other threads are blocked until the thread exits the protected code...

This does not mean that all threads are blocked, it just means all threads that are trying to synchronise on the same object (the _sharedResource in your example) are blocked.

The following quote is taken from Apple's Thread Programming Guide, which makes it clear that only threads that synchronise on the same object are blocked.

The object passed to the @synchronized directive is a unique identifier used to distinguish the protected block. If you execute the preceding method in two different threads, passing a different object for the anObj parameter on each thread, each would take its lock and continue processing without being blocked by the other. If you pass the same object in both cases, however, one of the threads would acquire the lock first and the other would block until the first thread completed the critical section.

Update: If your background threads are impacting the performance of your interface then you might want to consider putting some sleeps into the background threads. This should allow the main thread some time to update the UI.

I realise you are using GCD but, for example, NSThread has a couple of methods that will suspend the thread, e.g. -sleepForTimeInterval:. In GCD you can probably just call sleep().

Alternatively, you might also want to look at changing the thread priority to a lower priority. Again, NSThread has the setThreadPriority: for this purpose. In GCD, I believe you would just use a low priority queue for the dispatched blocks.

Upvotes: 3

JustSid
JustSid

Reputation: 25318

I'm not sure if I understood you correctly, @synchronize doesn't block all threads but only the ones that want to execute the code inside of the block. So the solution probably is; Don't execute the code on the main thread.

If you simply want to avoid having the main thread acquire the lock, you can do this (and wreck havoc):

dispatch_async(queue, ^
               {              
                   if(![NSThread isMainThread])
                   {
                       // Synchronized that shared resource
                       @synchronized(sharedResource_)
                       {   
                           // Write things on that resource
                           // If more that one thread access this piece of code:
                           // all threads (even main thread) will block until task is completed.
                           [self writeComplexDataOnLocalFile];
                       }          
                   }
                   else
                       [self writeComplexDataOnLocalFile];               
               });

Upvotes: 3

Related Questions