Is Objective-C's NSMutableArray thread-safe?

2019-01-12 22:07发布

I've been trying to fix this crash for almost a week. The application crashes without any exception or stack-trace. The application does not crash in any way while running through instruments in zombie mode.

I have a method that gets called on a different thread. The solution that fixed the crash was replacing

[self.mutableArray removeAllObjects];

with

dispatch_async(dispatch_get_main_queue(), ^{
    [self.searchResult removeAllObjects];
});

I thought it might be a timing issue, so I tried to synchronize it, but it still crashed:

@synchronized(self)
{
    [self.searchResult removeAllObjects];
}

Here is the code

- (void)populateItems
{
   // Cancel if already exists  
   [self.searchThread cancel];

   self.searchThread = [[NSThread alloc] initWithTarget:self
                                               selector:@selector(populateItemsinBackground)
                                                 object:nil];

    [self.searchThread start];
}


- (void)populateItemsinBackground
{
    @autoreleasepool
    {
        if ([[NSThread currentThread] isCancelled])
            [NSThread exit];

        [self.mutableArray removeAllObjects];

        // Populate data here into mutable array

        for (loop here)
        {
            if ([[NSThread currentThread] isCancelled])
                [NSThread exit];

            // Add items to mutableArray
        }
    }
}

Is this problem with NSMutableArray not being thread-safe?

7条回答
倾城 Initia
2楼-- · 2019-01-12 22:15

As others already said, NSMutableArray is not thread safe. In case anyone want to achieve more than removeAllObject in a thread-safe environment, I will give another solution using GCD besides the one using lock. What you have to do is to synchronize the read/update(replace/remove) actions.

First get the global concurrent queue:

dispatch_queue_t concurrent_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

For read:

- (id)objectAtIndex:(NSUInteger)index {
    __block id obj;
    dispatch_sync(self.concurrent_queue, ^{
        obj = [self.searchResult objectAtIndex:index];
    });
    return obj;
}

For insert:

- (void)insertObject:(id)obj atIndex:(NSUInteger)index {
    dispatch_barrier_async(self.concurrent_queue, ^{
        [self.searchResult insertObject:obj atIndex:index];
    });
}

From Apple Doc about dispatch_barrier_async:

When the barrier block reaches the front of a private concurrent queue, it is not executed immediately. Instead, the queue waits until its currently executing blocks finish executing. At that point, the barrier block executes by itself. Any blocks submitted after the barrier block are not executed until the barrier block completes.

Similar for remove:

- (void)removeObjectAtIndex:(NSUInteger)index {
    dispatch_barrier_async(self.concurrent_queue, ^{
        [self.searchResult removeObjectAtIndex:index];
    });
}

EDIT: Actually I found another simpler way today to synchronize access to a resource by using a serial queue provided by GCD.

From Apple Doc Concurrency Programming Guide > Dispatch Queues:

Serial queues are useful when you want your tasks to execute in a specific order. A serial queue executes only one task at a time and always pulls tasks from the head of the queue. You might use a serial queue instead of a lock to protect a shared resource or mutable data structure. Unlike a lock, a serial queue ensures that tasks are executed in a predictable order. And as long as you submit your tasks to a serial queue asynchronously, the queue can never deadlock.

Create your serial queue:

dispatch_queue_t myQueue = dispatch_queue_create("com.example.MyQueue", NULL);

Dispatch tasks async to the serial queue:

dispatch_async(myQueue, ^{
    obj = [self.searchResult objectAtIndex:index];
});

dispatch_async(myQueue, ^{
    [self.searchResult removeObjectAtIndex:index];
});

Hope it helps!

查看更多
混吃等死
3楼-- · 2019-01-12 22:25

Since serial queues were mentioned: With a mutable array, just asking "is it thread safe" isn't enough. For example, making sure that removeAllObjects doesn't crash is all good and fine, but if another thread tries to process the array at the same time, it will either process the array before or after all elements are removed, and you really have to think what the behaviour should be.

Creating one class + object that is responsible for this array, creating a serial queue for it, and doing all operations through the class on that serial queue is the easiest way to get things right without making your brain hurt through synchronisation problems.

查看更多
戒情不戒烟
4楼-- · 2019-01-12 22:27

As well as NSLock can also use @synchronized(condition-object) you just have to make sure every access of the array is wrapped in a @synchronized with the same object acting as the condition-object , if you only want to modify the contents of the same array instance then you can use the array itself as the condition-object, other wise you will have to use something else you know will not go away, the parent object, i.e self, is a good choice because it will always be the same one for the same array.

atomic in @property attributes will only make setting the array thread safe not modifying the contents, i.e. self.mutableArray = ... is thread safe but [self.mutableArray removeObject:] is not.

查看更多
Luminary・发光体
5楼-- · 2019-01-12 22:27
__weak typeof(self)weakSelf = self;

 @synchronized (weakSelf.mutableArray) {
     [weakSelf.mutableArray removeAllObjects];
 }

This amazing article will explain it all:

http://refactr.com/blog/2012/10/ios-tips-synchronized/

查看更多
The star\"
6楼-- · 2019-01-12 22:30

Almost NSMutable classes object is not thread safe.

查看更多
Ridiculous、
7楼-- · 2019-01-12 22:31

No.

It is not thread safe and if you need to modify your mutable array from another thread you should use NSLock to ensure everything goes as planned:

NSLock *arrayLock = [[NSLock alloc] init];

[...] 

[arrayLock lock]; // NSMutableArray isn't thread-safe
[myMutableArray addObject:@"something"];
[myMutableArray removeObjectAtIndex:5];
[arrayLock unlock];
查看更多
登录 后发表回答