【问题标题】:Is Objective-C's NSMutableArray thread-safe?Objective-C 的 NSMutableArray 是线程安全的吗?
【发布时间】:2012-08-19 08:17:44
【问题描述】:

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

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

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

【问题讨论】:

  • 一个 @synchronized 块本身不会做任何事情。你需要一个锁(这是@sychronized 提供的)围绕每个对数组的访问。
  • 为什么@autoreleasepool 在这里?有人可以澄清一下吗?
  • @MuraliMohan"如果你在 Application Kit 的主线程之外进行 Cocoa 调用——例如,如果你创建一个仅 Foundation 的应用程序或者如果你分离一个线程——你需要创建你自己的自动释放池。” developer.apple.com/reference/foundation/nsautoreleasepool
  • 在我看来,处理 reader/writers 访问问题的最好方法是使用 GCD (Grand Central Dispatch) 提供的 dispatch_barriers。

标签: objective-c ios multithreading nsmutablearray


【解决方案1】:
__weak typeof(self)weakSelf = self;

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

【讨论】:

  • 只是为了记录,您的同步句子本身没有任何意义。如果你用读写操作包装它以避免竞争条件,它确实有意义,但包装 removeAllObjects 方法不会增加任何价值。它应该在更高级别的用例中完成。
  • @GoRoS 不确定为什么它对您没有意义,因为它会创建互斥锁并阻止不同的线程访问此数组。
  • 这里你有一个简短的解释我的意思:stackoverflow.com/a/21139721/812598
  • @GoRos synchronized 只防止多个线程同时访问数组,并不能防止逻辑错误。基本上,如果您删除一个线程上的所有对象并尝试访问另一个线程上的任何对象,您仍然会崩溃。同步只会防止删除和访问对象不会同时发生。您可以在同步块内执行可能的操作。阅读文章。
  • 对不起,我一点也不同意。如果您使用答案代码删除所有对象并从另一个元素访问元素,它仍然会崩溃。它应该是具有更高业务级别用例的另一个线程的代码块,负责锁定可变数组,执行读/写操作并解锁它。这些操作之间什么都不会做。在您的链接中它是相同的,同步块包装了一个有意义的读写操作。查看之前的评论链接,找出我说错的原因。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    除了NSLock 也可以使用@synchronized(condition-object) 你只需要确保数组的每个访问都包含在@synchronized 中,并且具有相同的对象作用作为 condition-object ,如果你只想修改同一个数组实例的内容,那么你可以使用数组本身作为 condition-object,否则你会必须使用其他你知道不会消失的东西,父对象,即 self,是一个不错的选择,因为对于同一个数组,它总是相同的。

    @property 属性中的原子只会使设置数组线程安全而不修改内容,即 self.mutableArray = ... 是线程安全的,但 [self.mutableArray removeObject:] 不是。

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

        正如其他人已经说过的,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 提供的串行队列来同步对资源的访问。

        来自苹果文档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];
        });
        

        希望对您有所帮助!

        【讨论】:

        • 使用 gcd 可能会导致问题。如果希望在 runloop 结束之前将值删除/添加到错误中怎么办?
        • 只需对同一个串行队列执行所有操作。
        • 串行队列当然是解决方案,但是从任何类型的集合中读取线程安全的。并行读取和屏障修改非常好。
        • Jingjie,你的原始答案,使用屏障,是一个很好的答案,因为这是一种比已接受答案的锁定方法更有效的机制。它也比串行队列好。未来的读者应该在WWDC 2012 video, Asynchronous Design Patterns with Blocks, GCD, and XPC 中看到“Reader-Writer”讨论。但是请注意,永远不要在全局队列上使用障碍(注意对“私有”队列的引用)。使用自定义并发队列,例如concurrent_queue = dispatch_queue_create("com.domain.queuename", DISPATCH_QUEUE_CONCURRENT);.
        • 强调 Rob 所说的,你不仅不应该在全局队列上使用 dispatch_barrier,如果你这样做,它也不会正常工作。来自苹果:The queue you specify should be a concurrent queue that you create yourself using the dispatch_queue_create function. If the queue you pass to this function is a serial queue or one of the global concurrent queues, this function behaves like the dispatch_async function.
        【解决方案6】:

        没有。

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

        NSLock *arrayLock = [[NSLock alloc] init];
        
        [...] 
        
        [arrayLock lock]; // NSMutableArray isn't thread-safe
        [myMutableArray addObject:@"something"];
        [myMutableArray removeObjectAtIndex:5];
        [arrayLock unlock];
        

        【讨论】:

        • 您需要这样做使用相同的锁保护每个读取
        • 如果@property 声明时没有nonatomic 说明符,这是否必要?
        • 这完全取决于getter/setter的实现与否由于@synthesize,查看这篇文章:stackoverflow.com/a/589392/662605
        • 你是怎么做到的?你用锁包围了 removeAllObjects 吗?你的锁在哪里初始化?
        • 是的,你应该从你想要锁定的对象被初始化的线程初始化锁。尝试将锁放在初始化数组的位置旁边,并使其成为 ivar
        【解决方案7】:

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

        【讨论】:

          猜你喜欢
          • 2016-07-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-03-23
          • 2017-06-19
          • 1970-01-01
          • 1970-01-01
          • 2015-03-30
          相关资源
          最近更新 更多