Подтвердить что ты не робот

Блокировка объекта от доступа несколькими потоками - Objective-C

У меня вопрос о безопасности потоков в Objective-C. Я прочитал пару других ответов, некоторые из документации Apple, и все еще есть некоторые сомнения в этом, поэтому подумал, что я задам свой вопрос.

Мой вопрос три раза:

Предположим, что у меня есть массив, NSMutableArray *myAwesomeArray;

Сложить 1:

Теперь исправьте меня, если я ошибаюсь, но из того, что я понимаю, использование @synchronized(myAwesomeArray){...} предотвратит доступ двух потоков к одному и тому же блоку кода. Итак, в основном, если у меня есть что-то вроде:

-(void)doSomething {
    @synchronized(myAwesomeArray) {
        //some read/write operation on myAwesomeArray
    }
}

тогда, если два потока обращаются к одному и тому же методу с тем же временем, этот блок кода будет потокобезопасным. Я предполагаю, что я правильно понял эту часть.

Сложить 2:

Что мне делать, если к myAwesomeArray обращаются несколько потоков из разных методов? Если у меня есть что-то вроде:

- (void)readFromArrayAccessedByThreadOne {
    //thread 1 reads from myAwesomeArray
}

- (void)writeToArrayAccessedByThreadTwo {
    //thread 2 writes to myAwesomeArray
}

Теперь оба метода получают доступ к двум различным потокам одновременно. Как я могу гарантировать, что myAwesomeArray не будет иметь проблем? Использую ли я что-то вроде NSLock или NSRecursiveLock?

Сложить 3:

Теперь, в вышеупомянутых двух случаях, myAwesomeArray был iVar в памяти. Что делать, если у меня есть файл базы данных, который я не всегда храню в памяти. Я создаю databaseManagerInstance всякий раз, когда я хочу выполнять операции с базой данных, и отпустите его, как только закончите. Таким образом, в основном разные классы могут обращаться к базе данных. Каждый класс создает свой собственный экземпляр DatabaseManger, но в основном все они используют один и тот же файл с одной базой данных. Как я могу гарантировать, что данные не будут повреждены из-за условий гонки в такой ситуации?

Это поможет мне понять некоторые из моих основ.

4b9b3361

Ответ 1

Сложить 1 Обычно ваше понимание того, что делает @synchronized, является правильным. Однако технически он не делает код "потокобезопасным". Это предотвращает одновременное использование разных потоков одним и тем же замком, однако вам необходимо убедиться, что вы всегда используете один и тот же токен синхронизации при выполнении критических разделов. Если вы этого не сделаете, вы все равно можете оказаться в ситуации, когда два потока одновременно выполняют критические разделы. Проверьте документы.

Сложить 2 Большинство людей, вероятно, посоветуют вам использовать NSRecursiveLock. Если бы я был вами, я бы использовал GCD. Вот отличный документ, показывающий, как перейти от программирования потоков к программированию GCD. Я думаю, что этот подход к проблеме намного лучше, чем тот, который основан на на NSLock. В двух словах вы создаете очередную очередь и отправляете свои задачи в эту очередь. Таким образом, вы гарантируете, что ваши критические секции обрабатываются последовательно, поэтому в любой момент времени выполняется только один критический раздел.

Сложить 3 Это то же самое, что и Fold 2, только более конкретный. База данных - это ресурс, во многом это то же самое, что и массив или любая другая вещь. Если вы хотите увидеть подход на основе GCD в контексте программирования баз данных, взгляните на реализацию fmdb. Он делает то, что я описал в Fold2.

Как побочная заметка для Fold 3, я не думаю, что создание базы данных DatabaseManager каждый раз, когда вы хотите использовать базу данных, а затем освобождение - это правильный подход. Я думаю, вам нужно создать одно соединение с базой данных и сохранить его через сеанс приложения. Таким образом, проще управлять им. Опять же, fmdb - отличный пример того, как это можно достичь.

Edit Если вы не хотите использовать GCD, тогда да, вам нужно будет использовать какой-то механизм блокировки, и да, NSRecursiveLock предотвратит взаимоблокировки, если вы используете рекурсию в своих методах, поэтому это хороший выбор (используется @synchronized). Однако может быть один улов. Если возможно, что многие потоки будут ждать одного и того же ресурса, и порядок, в котором они получают доступ, имеет значение, тогда NSRecursiveLock недостаточно. Вы все еще можете справиться с этой ситуацией с помощью NSCondition, но поверьте мне, вы сэкономите много времени, используя GCD в этом случае. Если порядок потоков не имеет значения, вы можете быть уверены в блокировке.

Ответ 3

Подкласс NSMutableArray для обеспечения блокировки для методов доступа (чтения и записи). Что-то вроде:

@interface MySafeMutableArray : NSMutableArray { NSRecursiveLock *lock; } @end

@implementation MySafeMutableArray

- (void)addObject:(id)obj {
  [self.lock lock];
  [super addObject: obj];
  [self.lock unlock];
}

// ...
@end

Этот подход инкапсулирует блокировку как часть массива. Пользователям не нужно менять свои вызовы (но, возможно, им необходимо знать, что они могут блокировать/ждать доступа, если доступ критически важен). Существенным преимуществом такого подхода является то, что если вы решите, что не хотите использовать блокировки, вы можете повторно реализовать MySafeMutableArray для использования очередей отправки - или что-то лучшее для вашей конкретной проблемы. Например, вы можете реализовать addObject как:

- (void)addObject:(id)obj {
    dispatch_sync (self.queue, ^{ [super addObject: obj] });
}

Примечание. Если вы используете блокировки, вам наверняка понадобится NSRecursiveLock, а не NSLock, потому что вы не знаете реализации Objective-C addObject и т.д. сами рекурсивные.