Thread safety with NSOperation

Discussion in 'Mac Programming' started by Qaanol, Aug 25, 2013.

  1. Qaanol, Aug 25, 2013
    Last edited: Aug 25, 2013

    macrumors 6502a

    Joined:
    Jun 21, 2010
    #1
    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?
     
  2. macrumors 68040

    Joined:
    Feb 2, 2008
    #2


    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.
     
  3. Qaanol, Aug 25, 2013
    Last edited: Aug 25, 2013

    thread starter macrumors 6502a

    Joined:
    Jun 21, 2010
    #3

    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
    }
     
  4. macrumors 68040

    Joined:
    Feb 2, 2008
    #4
    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..
     
  5. thread starter macrumors 6502a

    Joined:
    Jun 21, 2010
    #5
    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.
     
  6. abhibeckert, Aug 25, 2013
    Last edited: Aug 25, 2013

    macrumors regular

    Joined:
    Jun 2, 2007
    #6
    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).
     

Share This Page