Multi-Threading question in Objective-C 2.0
Asked Answered
S

3

2

I have my main application delegate which contains a method that returns an object. This application delegate runs on the main thread.

I also have a NSOperation that gets run on a different thread. As well as wanting to be able to call my app delegate method on my main thread sometimes, I also need to call it from my NSOperation thread to get the object that it returns. My first question is, if I call this from my other thread...

id newObject = [[[UIApplication sharedApplication] delegate] myMethod];

... will that method be processed on the same thread as the NSOperation, or will it be the same thread (main) as the application delegate is on?

I also want to make sure that the code within myMethod is only called once at a time by either my operation thread or my main thread. Can I just create a NSLock instance var in my application delegate and do something like:

-(id)myMethod {
    [myLock lock];
    myObject = // Get or create my object to return
    [myLock unlock];
    return myObject;
}

Thanks for your help!

Mike

Sky answered 27/10, 2009 at 17:48 Comment(0)
F
11

Unless you explicitly write code to cause something to execute on another thread, every method call is going to be executed directly on the thread it was called upon. There is no magic with a method call. You can think of it as having the exact same semantics/ABI as a C function call for the purposes of threading.

Your locking pattern will work fine to ensure exclusive access across threads.

Two additional unrelated notes (because so many people trip over it):

  • declaring a property as atomic has little to do with thread safety. Atomicity only guarantees that you get a valid value, not the correct value (there is a difference).

  • autoreleased objects are never safe to pass between threads. You need an explicit retain on the sending thread and a balancing eventual release on the receiving thread.

Flavouring answered 27/10, 2009 at 17:50 Comment(2)
Would it be true to say that a @synchronized(self){} block would do the same thing as an NSLock instance?Sky
More or less; the implementation details are slightly different, but the impact is the same.Flavouring
O
3

Do you absolutely need to perform this invocation on the NSOperation thread, and not simply provide the required object as part of creating the custom operation?

If so, I would recommend not using locks unless performance is critical. If the iPhone supported it, you could use Grand Central Dispatch to get the object onto your thread:

__block id newObject = nil;
dispatch_sync(dispatch_get_main_queue(), ^{
    newObject = [[[[UIApplication sharedApplication] delegate] myMethod] retain];
});

For the iPhone, I would be tempted to create a helper method:

- (void)createNewObject:(NSValue *)returnPtr {
    id newObject = [[[[UIApplication sharedApplication] delegate] myMethod] retain];
    *(id *)[returnPtr pointerValue] = newObject;
}

And invoke it like so from your NSOperation thread:

id newObject = nil;
[self performSelectorOnMainThread:@selector(createNewObject:)
                       withObject:[NSValue valueWithPointer:&newObject]
                    waitUntilDone:YES];

Actually performing the execution on the main thread has fewer implicit risks.

Olsson answered 27/10, 2009 at 18:6 Comment(3)
That's an interesting approach. I did notice that the performSelectorOnMainThread method doesn't allow you to return a value, but I never thought of passing the address of a pointer! Out of interest, what does the cast *(id *) do? I've seen a cast such as (NSNumber *) but I thought that the id type didn't need a pointer asterix. And what is the first asterix for?Sky
If you really want to do blocks/GCD on iPhone check out the PLBlocks and WiganWallgate projects.Birdhouse
The cast (id *) makes sure that the void * returned by the -pointerValue method is treated as a pointer to an id. The first * dereferences it, so that you alter the target of the pointer rather than the pointer itself.Olsson
A
2

If you simply need to protect a critical section of code, why not using the Objective-C @synchronized directive? Of course, using NSLock will also work, but you need to explicitly manage the NSLock instance. From the documentation:

Objective-C supports multithreading in applications. This means that two threads can try to modify the same object at the same time, a situation that can cause serious problems in a program. To protect sections of code from being executed by more than one thread at a time, Objective-C provides the @synchronized() directive.

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.

The @synchronized() directive takes as its only argument any Objective-C object, including self. This object is known as a mutual exclusion semaphore or mutex. It allows a thread to lock a section of code to prevent its use by other threads. You should use separate semaphores to protect different critical sections of a program. It’s safest to create all the mutual exclusion objects before the application becomes multithreaded to avoid race conditions.

Listing 12-1 shows an example of code that uses self as the mutex to synchronize access to the instance methods of the current object. You can take a similar approach to synchronize the class methods of the associated class, using the Class object instead of self. In the latter case, of course, only one thread at a time is allowed to execute a class method because there is only one class object that is shared by all callers.

Listing 12-1 Locking a method using self

- (void)criticalMethod
{
    @synchronized(self) {
        // Critical code.
        ...
    }
}
Aegospotami answered 27/10, 2009 at 19:46 Comment(1)
That looks quite promising. The only thing I don't quite understand is the semaphone object that you pass to it. I don't get what it's used for!?Sky

© 2022 - 2024 — McMap. All rights reserved.