Вопрос по ios, multithreading, objective-c – Блокировка объекта от доступа нескольких потоков - Objective-C

36

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

Мой вопросthree fold:

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

Fold 1:

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

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

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

Fold 2:

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

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

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

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

Fold 3:

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

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

Ах. Понимаю. Ну, я думаю, что было немного больше в том, что я понял о директиве @synchronize. Спасибо! : D codeBearer
@synchronize предотвращает доступ других потоков к той же переменной, которую вы заблокировали, а не к конкретному блоку кода. Richard J. Ross III
@codeBearer Я ответил на ваш вопрос наstackoverflow.com/a/15393623/412916 Своего рода. Jano

Ваш Ответ

3   ответа
1

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

@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, потому что вы не знаете, какие реализации addObject и т. Д. Objective-C сами по себе являются рекурсивными.

Потрясающие. Спасибо за этот совет. Я буду смотреть в него. : D codeBearer
Спасибо за ваш ответ. Это хорошая идея, если бы я сделал потокобезопасный подкласс объекта. :) codeBearer
Любой подклассNSArray необходимо также реализовать несколько других методов; это не хороший пример.
@ilyan. Ваш комментарий не является хорошим, если вы не предоставите какие-либо подробности, подтверждающие вашу претензию. Не могли бы Вы уточнить?
Если вы оставляете безопасность потоков до вызывающих (то есть вы не создаете потокобезопасный подкласс, как когда-либо реализованный), то вы, повторяю, настроите себя на проблемы. Вы бы хотели немного узнать о технологии Spin. Удачи.
43

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

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

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

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

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

Рад помочь, взгляните на мой отредактированный ответ.
Большое спасибо за ваш ответ! Я думаю, это решает большинство моих сомнений. Как подтверждение: я предполагаю, что я используюNSRecursiveLock для Fold 3, если бы я не использовал GCD, верно? Это больше для моей базы знаний, так что мои концепции ясны. Кроме того, спасибо за вашу заметку оFold 3, Я согласен с вами в том, что лучше сохранить класс, а также открывать и закрывать соединение по мере необходимости. Я просто переписал некоторый код, который выполнял несколько экземпляров - какой это был кошмар. & GT; _ & л; Я поставил это в вопросе, чтобы дать лучшую картину. :) codeBearer
В варианте сгиба 2 вы можете использовать шаблон чтения-записи (параллельная очередь, чтение сdispatch_syncнаписать сdispatch_barrier_async) как обсуждено вWWDC 2012 video - Asynchronous Design Patterns.
Отлично! Спасибо большое снова! : D myKnowledge ++ Я планировал начать использовать GCD во всех наших новых проектах, и предоставленные вами ссылки - отличная отправная точка! :) codeBearer
+1 для GCD и Serial Queue. Один хороший способ избежать проблем
5

Swift 3 вWWDC 2016 Session Session 720 Параллельное программирование с GCD в Swift 3, вы должны использоватьqueue

class MyObject {
  private let internalState: Int
  private let internalQueue: DispatchQueue

  var state: Int {
    get {
      return internalQueue.sync { internalState }
    }

    set (newValue) {
      internalQueue.sync { internalState = newValue }
    }
  }
}

Похожие вопросы