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

ary*_*axt 50 multithreading objective-c nsmutablearray ios

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

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

[self.mutableArray removeAllObjects];
Run Code Online (Sandbox Code Playgroud)

dispatch_async(dispatch_get_main_queue(), ^{
    [self.searchResult removeAllObjects];
});
Run Code Online (Sandbox Code Playgroud)

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

@synchronized(self)
{
    [self.searchResult removeAllObjects];
}
Run Code Online (Sandbox Code Playgroud)

这是代码

- (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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Dan*_*iel 87

没有.

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

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

[...] 

[arrayLock lock]; // NSMutableArray isn't thread-safe
[myMutableArray addObject:@"something"];
[myMutableArray removeObjectAtIndex:5];
[arrayLock unlock];
Run Code Online (Sandbox Code Playgroud)

  • 你需要这样做**和**保护每个*read*同样的锁. (29认同)
  • 是的,您应该从线程初始化锁定初始化要锁定的对象.尝试将锁放在初始化阵列的位置旁边,然后将其设置为ivar (2认同)

Jin*_*han 36

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

首先获取全局并发队列:

dispatch_queue_t concurrent_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
Run Code Online (Sandbox Code Playgroud)

如需阅读:

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

对于插入:

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

来自Apple Doc的dispatch_barrier_async:

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

类似于删除:

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

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

从Apple Doc 并发编程指南>调度队列:

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

创建串行队列:

dispatch_queue_t myQueue = dispatch_queue_create("com.example.MyQueue", NULL);
Run Code Online (Sandbox Code Playgroud)

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

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

dispatch_async(myQueue, ^{
    [self.searchResult removeObjectAtIndex:index];
});
Run Code Online (Sandbox Code Playgroud)

希望能帮助到你!

  • Jingjie,你的原始答案,使用屏障,是一个很好的答案,因为这是一个比接受答案的锁定方法更有效的机制.它也比串行队列更好.未来的读者应该在[WWDC 2012视频,带块的异步设计模式,GCD和XPC]中看到"读者 - 作者"讨论(https://developer.apple.com/videos/wwdc/2012/?id=712).但是请注意,不应该在全局队列上使用障碍(注意对"私有"队列的引用).使用自定义并发队列,例如`concurrent_queue = dispatch_queue_create("com.domain.queuename",DISPATCH_QUEUE_CONCURRENT);`. (13认同)
  • 强调Rob所说的,不仅你不应该在全局队列上使用dispatch_barrier,如果你这样做也不会正常工作.来自Apple:`您指定的队列应该是您使用dispatch_queue_create函数自己创建的并发队列.如果传递给此函数的队列是串行队列或全局并发队列之一,则此函数的行为类似于dispatch_async函数. (2认同)

Nat*_*Day 19

除了也NSLock可以使用@synchronized(condition-object)之外,如果你只想修改同一个数组实例的内容,你必须确保数组的每次访问都包含在一个@synchronized同一个对象中作为条件对象.然后你可以使用数组本身作为条件对象,否则你将不得不使用你知道不会消失的其他东西,父对象,即自我,是一个很好的选择,因为它总是相同的相同的数组.

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


sas*_*ash 12

__weak typeof(self)weakSelf = self;

 @synchronized (weakSelf.mutableArray) {
     [weakSelf.mutableArray removeAllObjects];
 }
Run Code Online (Sandbox Code Playgroud)

这篇惊人的文章将解释这一切:

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


gna*_*729 5

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

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