PDA

View Full Version : Thread safety with NSOperation




Qaanol
Aug 25, 2013, 09:34 AM
I have an object, myObject, which is going to spawn a number of NSOperations. Let’s say N of them. Each NSOperation will have an int idNumber from 0 to N-1, and a reference to myObject.

The NSOperations each calculate a float. I want myObject to store the floats, and do something when all N operations are done. So I am wondering if it is thread-safe to have as instance variables:

float results[N];
int numFinished;

And a method
-(void)operationDone:(id)sender
{
results[ [sender idNumber] ] = [sender result];
if (++numFinished == N) [self processAllResults];
}

So each NSOperation calls [myObject operationDone:self] when it is done calculating.

I guess my question has two parts: is it safe to modify results[i] from multiple threads provided the "i" is different for each thread? And is it safe to increment numFinished from multiple threads the way I describe?

If not, what is a good way to track the results of N operations and do some work when they are all finished?

Edit: In case it matters, myObject will itself have been spawned by an NSOperation.

Edit 2: I could, of course, make a dummy method for the operations to call, which just calls operationDone on the main thread. However, I do not want to call [myObject processAllResults] on the main thread because it will take a while. Is a solution then:

-(void)operationDone:(id)sender
{
[self performSelectorOnMainThread:@selector(updateResults)
withObject:sender
waitUntilDone:YES];
if (numFinished == N) [self processAllResults];
}

-(void)updateResults:(id)sender
{
results[ [sender idNumber] ] = [sender result];
numFinished = numFinished + 1;
}

Edit 3: Ideally I’d prefer to perform the selector not on the main thread, but on the particular thread running the NSOperation that spawned myObject. If I have a reference to that NSOperation, is there a way to fetch the thread it is running on?



subsonix
Aug 25, 2013, 09:57 AM
I guess my question has two parts: is it safe to modify results[i] from multiple threads provided the "i" is different for each thread? And is it safe to increment numFinished from multiple threads the way I describe?


If I understand you correctly I can't see why results[i] could not be modified as each index is unique to a particular thread. I do think you would need to protect numFinished with a mutex lock if it's a shared resource though.

Qaanol
Aug 25, 2013, 10:06 AM
If I understand you correctly I can't see why results[i] could not be modified as each index is unique to a particular thread. I do think you would need to protect numFinished with a mutex lock if it's a shared resource though.
Is it not sufficient to declare numFinished as volatile?

Edit: Oh, I see, part of the problem is if the last two operations finish at the same time, then numFinished might get incremented by both before either checks whether it equals N. If that happens then processAllResults would get called twice. So I really need

-(void)operationDone:(id)sender
{
results[ [sender idNumber] ] = [sender result];
// acquire a lock
if (++numFinished == N) [self processAllResults];
// release the lock
}

subsonix
Aug 25, 2013, 10:18 AM
Is it not sufficient to declare numFinished as volatile?

I think you need to protect it, declaring it volatile would not do that on it's own, that said I have no experience with NSOperations but pthreads and GCD, but the rule afaik is to always protect shared resources. A quick google came up with a @syncronized directive, perhaps look into that. Someone else may also have something to say here..

Qaanol
Aug 25, 2013, 11:09 AM
Okay, thanks.

Looking at the documentation again, it seems for the particular program Iím writing, I can give myObject a reference to the NSOperationQueue holding the other operations, and have myObject call [theQueue waitUntilAllOperationsAreFinished] rather than keeping count of how many have finished.

abhibeckert
Aug 25, 2013, 04:36 PM
I would create an NSOperation for each array index. Just off the top of my head, you want something like:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

for (i = 0; i < m; i++) {
int queueIndex = i; // make a copy of the variable just for the block
[queue addBlockOperation:^{
results[queueIndex] = [self calculateResult:queueIndex];
}];
}
[queue waitUntilAllOperationsFinished];

[self doFinalProcessing];

The default is to perform as many operations concurrently as apropriate for your CPU and the state of other currently running processes. You can also reduce this by configuring the queue with a maximum number of concurrent operations (A good idea if you're reading the hard drive or performing network activity... because those will make the operation idle while it waits, causing a new operation to start on the idle CPU core and you could end up with thousands of simultaneous network requests).