Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

Qaanol

macrumors 6502a
Original poster
Jun 21, 2010
571
11
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
Code:
-(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 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:

Code:
-(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?
 
Last edited:
I guess my question has two parts: is it safe to modify results 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 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.
 
If I understand you correctly I can't see why results 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

Code:
-(void)operationDone:(id)sender
{
  results[ [sender idNumber] ] = [sender result];
  // acquire a lock
  if (++numFinished == N) [self processAllResults];
  // release the lock
}
 
Last edited:
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..
 
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.
 
I would create an NSOperation for each array index. Just off the top of my head, you want something like:

Code:
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).
 
Last edited:
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.