52

我已经尝试修复这个崩溃了将近一个星期。应用程序崩溃,没有任何异常或堆栈跟踪。在僵尸模式下通过仪器运行时,应用程序不会以任何方式崩溃。

我有一个在不同线程上调用的方法。修复崩溃的解决方案是更换

[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 的这个问题不是线程安全的吗?

4

7 回答 7

90

不。

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

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

[...] 

[arrayLock lock]; // NSMutableArray isn't thread-safe
[myMutableArray addObject:@"something"];
[myMutableArray removeObjectAtIndex:5];
[arrayLock unlock];
于 2012-08-23T18:43:51.183 回答
39

正如其他人已经说过的, 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];
});

希望能帮助到你!

于 2013-07-31T22:16:30.340 回答
21

除了NSLock可以使用@synchronized条件对象)之外,如果您只想修改同一个数组实例的内容,您只需确保数组的每次访问都包含在一个@synchronized与充当条件对象的对象相同的对象中那么你可以使用数组本身作为条件对象,否则你将不得不使用其他你知道不会消失的东西,父对象,即 self,是一个不错的选择,因为它对于相同的数组。

atomic in @propertyattributes 只会使设置数组线程安全而不修改内容,即self.mutableArray= ... 是线程安全的,但[self.mutableArray removeObject:]不是。

于 2012-09-07T12:05:10.350 回答
14
__weak typeof(self)weakSelf = self;

 @synchronized (weakSelf.mutableArray) {
     [weakSelf.mutableArray removeAllObjects];
 }
于 2014-05-02T19:38:44.113 回答
5

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

创建一个负责该数组的类 + 对象,为其创建一个串行队列,并通过该串行队列上的类执行所有操作,这是最简单的方法,无需因同步问题而伤脑筋。

于 2014-02-18T23:54:21.950 回答
1

所有 NSMutablexxx 类都不是线程安全的。包括get、insert、remove、add和replace在内的操作都应该和NSLock一起使用。这是苹果给出的线程安全和线程不安全类的列表:线程安全总结

于 2016-08-17T15:23:45.927 回答
0

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

于 2012-08-23T18:44:26.637 回答