1

If I have two different threads via GCD accessing an NSMutableArray and one is merely creating a new array based off the mutable array while the other thread is deleting records from the array, should I expect this to be a problem? That is, shouldn't the copy, which I presume is merely "reading" the array, just get whatever happens to be in the array at that moment? I am not enumerating the array in either thread, but it is still crashing. As soon as I remove the read routine, it works fine.

Here is the "read" :

  dispatch_async(saveQueue, ^{

    NSDictionary*tempstocks=[NSDictionary dictionaryWithDictionary:self.data];

It crashes on this thread with: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[9]'

Here is what is happening on another thread:

[self.data removeObjectForKey:item];

I know you cannot mutate while enumerating, but I'd think it would be okay to read while mutating, you might not know which version of the mutated object you get, but I wouldn't think this is a problem, but clearly it is. Perhaps the dictionaryWithDictionary method is performing an operation that first sees X objects but by the time the routine is done it contains X-Y objects, thus it is not "capturing" the entire self.data dictionary in one snap when it runs dictionaryWithDictionary and is instead enumerating over self.data which would essentially be the same problem as mutation while enumeration?

johnbakers
  • 24,158
  • 24
  • 130
  • 258

3 Answers3

5

I guess that you might create three different queues using GCD: one for save, second one for something else and last one to operate with NSMutableArray.

dispatch_async(saveQueue, ^{
    dispatch_barrier_async(_queue, ^{
            NSDictionary*tempstocks=[NSDictionary dictionaryWithDictionary:self.data];
        });
});

dispatch_async(anotherQueue, ^{
    dispatch_barrier_async(_queue, ^{
            [self.data removeObjectForKey:item];
        });
});

It's like @synchronize but using GCD.

More info: GCD Reference/dispatch_barrier_async and http://www.mikeash.com/pyblog/friday-qa-2011-10-14-whats-new-in-gcd.html

EDIT

I have made a couple of performance test in order to understand which of the way is faster:

- (void)usingSynchronized
{
    dispatch_queue_t writeQyeue = dispatch_queue_create("com.tikhop.writeQyeue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(writeQyeue, ^{
        for(size_t i=0; i<10000; i++)
            @synchronized (arr) {
                [arr replaceObjectAtIndex:0 withObject:[NSNumber numberWithInt:1]];
                [arr replaceObjectAtIndex:0 withObject:[NSNumber numberWithInt:2]];
                [arr replaceObjectAtIndex:0 withObject:[NSNumber numberWithInt:3]];
                [arr replaceObjectAtIndex:0 withObject:[NSNumber numberWithInt:4]];
            }
    });
}

- (void)usingGCD
{
    dispatch_queue_t writeQyeue = dispatch_queue_create("com.tikhop.writeQyeue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(writeQyeue, ^{
        for(size_t i=0; i<10000; i++)
            dispatch_barrier_async(_queue, ^{
                [arr replaceObjectAtIndex:0 withObject:[NSNumber numberWithInt:5]];
                [arr replaceObjectAtIndex:0 withObject:[NSNumber numberWithInt:6]];
                [arr replaceObjectAtIndex:0 withObject:[NSNumber numberWithInt:7]];
                [arr replaceObjectAtIndex:0 withObject:[NSNumber numberWithInt:8]];
            });
    });
}

arr = [NSMutableArray arrayWithCapacity:1];
[arr addObject:@(0)];

[self usingSynchronized];
[self usingGCD];

I got the following result: enter image description here

tikhop
  • 2,012
  • 14
  • 32
  • 1
    I like this. Most of the docs and advice I read suggest using GCD as much as possible at the exclusion of `@synchronize` so it's a good suggestion – johnbakers Aug 12 '12 at 09:59
  • your answer makes good sense to me but I'll admit it's also very easy to do what is suggested here: http://stackoverflow.com/a/4676307/768472 Do you think that answer provides enough of a solution for using an `@synchronize` method? – johnbakers Aug 12 '12 at 10:46
  • I guess that both approach doing the same, but GCD is faster. – tikhop Aug 12 '12 at 11:26
  • Nice test! what is the `@(0)` in your `addObject` method? Also, is `_queue` defined as a concurrent queue? – johnbakers Aug 12 '12 at 12:07
  • Yes, _queue is concurrent queue – tikhop Aug 12 '12 at 12:18
  • I suppose the `@synchronize` approach has one advantage in that it is easily used to lock access to an object from several other objects that might need to read/write/mutate the affected item, while queues are usually used only by the object that creates them – johnbakers Aug 12 '12 at 12:19
1

You cannot assume that any operation on NSDictionary is thread-safe. And almost all f them are not. You really need to set up a mutex, @synchronize access to your array or use a gcd serial queue for access.

Farcaller
  • 3,070
  • 1
  • 27
  • 42
0

dictionaryWithDictionary: is internally enumerating the argument, so you are basically mutating while enumerating.

Also, in general, you should never write to an object if another thread is going to access it in any way unless you use some sort of synchronization primitive.

Your reasoning that it "reads" whatever it's there at the moment is not valid in general. Here is a little more info on the problems inherent in multithreading Usage of registers by the compiler in multithreaded program

Community
  • 1
  • 1
Analog File
  • 5,280
  • 20
  • 23