Objective-C的NSMutableArray是否是线程安全的?

时间:2012-08-23 18:36:53

标签: objective-c ios multithreading nsmutablearray

我一直试图解决这个崩溃已近一周了。应用程序崩溃,没有任何异常或堆栈跟踪。在僵尸模式下通过仪器运行时,应用程序不会以任何方式崩溃。

我有一个在不同线程上调用的方法。 修复崩溃的解决方案正在取代

[self.mutableArray removeAllObjects];

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

我认为这可能是一个时间问题,所以我尝试同步它,但它仍然崩溃了:

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

这是代码

- (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
        }
    }
}

NSMutableArray的这个问题不是线程安全的吗?

7 个答案:

答案 0 :(得分:85)

没有

它不是线程安全的,如果你需要从另一个线程修改你的可变数组,你应该使用NSLock来确保一切按计划进行:

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

[...] 

[arrayLock lock]; // NSMutableArray isn't thread-safe
[myMutableArray addObject:@"something"];
[myMutableArray removeObjectAtIndex:5];
[arrayLock unlock];

答案 1 :(得分:36)

正如其他人已经说过的那样,NSMutableArray不是线程安全的。如果有人想要在线程安全的环境中实现多于removeAllObject,除了使用锁定之外,我将使用GCD提供另一种解决方案。您需要做的是同步读取/更新(替换/删除)操作。

首先获取全局并发队列:

dispatch_queue_t concurrent_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

阅读:

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

对于插入:

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

来自Apple Doc的dispatch_barrier_async:

  

当屏障块到达专用并发队列的前面时,它不会立即执行。相反,队列等待,直到其当前正在执行的块完成执行。此时,屏障块自行执行。在屏障块完成之后,在屏障块之后提交的任何块都不会执行。

类似于删除:

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

编辑:实际上我今天找到了另一种更简单的方法,即使用GCD提供的串行队列来同步对资源的访问。

来自Apple Doc Concurrency Programming Guide > Dispatch Queues

  

当您希望任务按特定顺序执行时,串行队列非常有用。串行队列一次只执行一个任务,并始终从队列的头部提取任务。您可以使用串行队列而不是锁来保护共享资源或可变数据结构。与锁不同,串行队列确保以可预测的顺序执行任务。只要您将任务异步提交到串行队列,队列就永远不会死锁。

创建序列队列:

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

将任务异步调度到串行队列:

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

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

希望它有所帮助!

答案 2 :(得分:19)

NSLock也可以使用@synchronized条件对象)之外,您只需要确保数组的每次访问都包含在{{1}中使用相同的对象充当 condition-object ,如果您只想修改同一个数组实例的内容,那么您可以将该数组本身用作 condition-object ,另一方面,你将不得不使用你知道不会消失的其他东西,父对象,即self,是一个很好的选择,因为对于同一个数组,它总是相同的。

@synchronized属性中的

原子只会使数组线程安全而不修改内容,即@property = ...是线程安全的,但self.mutableArray不是。

答案 3 :(得分:12)

__weak typeof(self)weakSelf = self;

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

这篇精彩的文章将解释这一切:

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

答案 4 :(得分:5)

由于提到了串行队列:使用可变数组,只是询问“它是否是线程安全的”是不够的。例如,确保removeAllObjects没有崩溃是好的,但是如果另一个线程试图同时处理该数组,它将在或之前处理数组 所有元素都被删除了,你真的必须考虑应该是什么行为。

创建一个负责此数组的类+对象,为其创建一个串行队列,并通过该串行队列上的类执行所有操作是最简单的方法,可以在不通过同步问题让您的大脑受到伤害的情况下做到正确。

答案 5 :(得分:0)

几乎NSMutable类对象不是线程安全的。

答案 6 :(得分:0)

所有NSMutablexxx类都不是线程安全的。包含get,insert,remove,add和replace的操作应该与NSLock一起使用。这是一个由apple提供的线程安全和线程不安全的类列表:Thread Safety Summary