No crash when mutating an array from two different threads using global concurrent queues

266 Views Asked by At

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
2

There are 2 best solutions below

0
On

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.

0
On

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.