Является 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:]
не является.
Ответ 4
__weak typeof(self)weakSelf = self;
@synchronized (weakSelf.mutableArray) {
[weakSelf.mutableArray removeAllObjects];
}
Эта удивительная статья объяснит все это:
http://refactr.com/blog/2012/10/ios-tips-synchronized/
Ответ 5
Так как были упомянуты последовательные очереди: с измененным массивом просто спрашивать "он потокобезопасен" недостаточно. Например, убедитесь, что removeAllObjects не сбой, все хорошо и хорошо, но если другой поток пытается обработать массив одновременно, он либо обработает массив до, либо после удаления всех элементов, и вам действительно нужно будет подумайте, каково должно быть поведение.
Создание одного класса + объекта, отвечающего за этот массив, создание для него последовательной очереди и выполнение всех операций с помощью класса в этой последовательной очереди - это самый простой способ получить все правильно, не заставляя ваш мозг повреждаться из-за проблем с синхронизацией.
Ответ 6
Объект почти NSMutable классов не является потокобезопасным.
Ответ 7
Все классы NSMutablexxx не являются потокобезопасными. Операции, включая получение, вставку, удаление, добавление и замену, должны использоваться с NSLock. Это список потокобезопасных и небезопасных классов, заданных apple: Thread Резюме безопасности