0

In the below code we have a mutable array, which is being mutated by two concurrent queues. Since concurrent queues are not thread safe, this code should ideally crash but this gets executed without any exception or crash.

Kindly help me in understanding this behaviour. Any help will be much appreciated :-)

    @interface ViewController ()
    @property(nonatomic, strong) NSMutableArray *arr;
    @end

    @implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.arr = [NSMutableArray new];
}

-(void)viewDidAppear:(BOOL)animated{

        [super viewDidAppear:animated];

        __weak typeof(self) weakSelf = self;

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            for (int i = 0; i < 20000; i++) {
                    [weakSelf.arr addObject:[NSNumber numberWithInt:i]];
                    NSLog(@"Added %@", [weakSelf.arr lastObject]);
                }

            NSLog(@"Final count %ld", [self.arr count]);
        });

        [self performSelector:@selector(removeObjects) withObject:nil afterDelay:0.1];
    }

    -(void)removeObjects{
        __weak typeof(self) weakSelf = self;

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            for (int i = 0; i < 1000; i++) {
                if (weakSelf.arr.count > 1) {
                    [weakSelf.arr removeObjectAtIndex:0];
                }
                NSLog(@"Remove object");
            }
        });
    }

    @end
Say2Manuj
  • 709
  • 10
  • 20
  • Perhaps you're just being lucky. I might be wrong, but I'm not sure your code is guaranteed to crash; I think it's just not guaranteed to not crash... By definition, you can't rely on race conditions to be consistent... – Nicolas Miari May 24 '17 at 02:21
  • @NicolasMiari Thanks for your input. I tried running this code several times, not for once it threw an exception. I can't be lucky every time. I tried to figure out various possibilities which might be helping in avoiding an exception, like block scope but I am unable to conclude anything which may be accurate. – Say2Manuj May 24 '17 at 02:50
  • Where do you actually allocate an array? `arr` is nil so your code doesn't do anything – Paulw11 May 24 '17 at 03:05
  • @Paulw11: I am allocating the array, for brevity I just skipped mentioning that line of code in the question. I have edited my question to include the allocation. – Say2Manuj May 24 '17 at 03:10

2 Answers2

0

Having concurrent access to one resource does not mean, that a crash is guaranteed. This has nothing to do with "having luck". Maybe your code runs systematically in a situation, where no crash occurs.

Moreover being "not thread-safe" does not mean "will crash". Any malfunction can occur.

Amin Negm-Awad
  • 16,582
  • 3
  • 35
  • 50
0

Concurrent access to an NSMutableArray won't explicitly cause a crash but it can lead to corruption of your array which can cause your app to crash as a result of invalid data.

Consider this slightly modified version of your code:

-(void)viewDidAppear:(BOOL)animated{

    [super viewDidAppear:animated];

    __weak typeof(self) weakSelf = self;

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        for (int i = 0; i < 20000; i++) {
           NSUInteger randomIndex = arc4random_uniform([weakSelf.arr count]);
            [weakSelf.arr insertObject:[NSNumber numberWithInt:i] atIndex:randomIndex];
            NSLog(@"Added %@", weakSelf.arr[randomIndex]);
        }

        NSLog(@"Final count %ld", [self.arr count]);
    });

    [self performSelector:@selector(removeObjects) withObject:nil afterDelay:0.1];
}

-(void)removeObjects{
    __weak typeof(self) weakSelf = self;

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        for (int i = 0; i < 1000; i++) {
            if (weakSelf.arr.count > 1) {
                 NSUInteger randomIndex = arc4random_uniform([weakSelf.arr count]);
                [weakSelf.arr removeObjectAtIndex:randomIndex];
            }
            NSLog(@"Remove object");
        }
    });
}

Each time your run it you get a slightly different value for the final count.

If you add @synchronized to make the array access thread-safe then you always get a final count of 19000

-(void)viewDidAppear:(BOOL)animated{

    [super viewDidAppear:animated];

    __weak typeof(self) weakSelf = self;

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        for (int i = 0; i < 20000; i++) {
            @synchronized (self.arr) {

           NSUInteger randomIndex = arc4random_uniform([weakSelf.arr count]);
            [weakSelf.arr insertObject:[NSNumber numberWithInt:i] atIndex:randomIndex];
            NSLog(@"Added %@", weakSelf.arr[randomIndex]);
            }
        }

        NSLog(@"Final count %ld", [self.arr count]);
    });

    [self performSelector:@selector(removeObjects) withObject:nil afterDelay:0.1];
}

-(void)removeObjects{
    __weak typeof(self) weakSelf = self;

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        for (int i = 0; i < 1000; i++) {
            if (weakSelf.arr.count > 1) {
                @synchronized (self.arr) {

                 NSUInteger randomIndex = arc4random_uniform([weakSelf.arr count]);
                [weakSelf.arr removeObjectAtIndex:randomIndex];
                }
            }
            NSLog(@"Remove object");
        }
    });
}

Mutating an array while you are using fast enumeration will give a crash, but the mutation doesn't even have to be from another thread in that case; simply mutating the array in the enumeration loop will cause the crash.

Paulw11
  • 108,386
  • 14
  • 159
  • 186