Является Objective-C 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 небезопасной для потоков?

Ответ 1

Нет.

Он не является потокобезопасным, и если вам нужно изменить свой изменяемый массив из другого потока, вы должны использовать NSLock, чтобы все прошло как запланировано:

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

[...] 

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

Ответ 2

Как уже говорили другие, 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];
    });
}

EDIT. Фактически, сегодня я нашел еще один более простой способ синхронизации доступа к ресурсу с помощью последовательной очереди, предоставляемой GCD.

От Apple Doc Concurrency Руководство по программированию > Очереди отправки:

Последовательные очереди полезны, когда вы хотите, чтобы ваши задачи выполнялись в определенном порядке. Последовательная очередь выполняет только одну задачу за раз и всегда вытягивает задачи из головы очереди. Вы можете использовать последовательную очередь вместо блокировки для защиты общего ресурса или изменяемой структуры данных. В отличие от блокировки, последовательная очередь обеспечивает выполнение задач в предсказуемом порядке. И до тех пор, пока вы отправляете свои задачи в последовательную очередь асинхронно, очередь никогда не сможет затормозить.

Создайте свою последовательную очередь:

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

Задачи отправки async для последовательной очереди:

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

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

Надеюсь, это поможет!

Ответ 3

Как и NSLock, можно также использовать @synchronized (условие-объект), вам просто нужно убедиться, что каждый доступ массива завернут в @synchronized тем же объектом, что и объект условия, если вы только хотите изменить содержимое одного и того же экземпляра массива, тогда вы можете использовать сам массив как объект условия, другой разумный вам придется использовать что-то другое, что вы знаете, не исчезнет, ​​родительский объект, то есть сам, является хороший выбор, потому что он всегда будет одним и тем же для одного и того же массива.

Атрибуты

atomic в @property будут только сделать установку потока массива безопасным, не изменяя содержимое, т.е. self.mutableArray =... является потокобезопасным, но [self.mutableArray removeObject:] не является.

Ответ 5

Так как были упомянуты последовательные очереди: с измененным массивом просто спрашивать "он потокобезопасен" недостаточно. Например, убедитесь, что removeAllObjects не сбой, все хорошо и хорошо, но если другой поток пытается обработать массив одновременно, он либо обработает массив до, либо после удаления всех элементов, и вам действительно нужно будет подумайте, каково должно быть поведение.

Создание одного класса + объекта, отвечающего за этот массив, создание для него последовательной очереди и выполнение всех операций с помощью класса в этой последовательной очереди - это самый простой способ получить все правильно, не заставляя ваш мозг повреждаться из-за проблем с синхронизацией.

Ответ 6

Объект почти NSMutable классов не является потокобезопасным.

Ответ 7

Все классы NSMutablexxx не являются потокобезопасными. Операции, включая получение, вставку, удаление, добавление и замену, должны использоваться с NSLock. Это список потокобезопасных и небезопасных классов, заданных apple: Thread Резюме безопасности