2012-04-15 2 views
3

У меня есть этот (редкий) случайный случай, когда моя программа iOS iOS-i блокируется. Когда я ворвался в отладчик, есть два потока, и оба они застревают в @synchronized().Заблокировано в ожидании @синхронизировано

Если я полностью не понимаю @synchronized, я не думал, что это возможно и весь смысл команды.

У меня есть основной поток и рабочий поток, которым нужен доступ к базе данных sqlite, поэтому я обертываю куски кода, которые обращаются к db в блоках @synchronized (myDatabase). В этих блоках больше ничего не происходит, кроме доступа к db.

Я также использую фреймворк FMDatabase для доступа к sqlite, я не знаю, имеет ли это значение.

myDatabase - глобальная переменная, содержащая объект FMDatabase. Он создается один раз в начале программы.

+0

Просто, чтобы сузить проблему - попробуйте заменить ваш @synchronized на [замок блокировки] и [блокировки разблокировки] на общем случае NSLock. – Stavash

+0

Это легче сказать, чем сделать. :-) Эта блокировка происходит примерно раз в неделю с использованием сотен часов при использовании между ними. Также я понимаю, что то, о чем вы говорите, именно то, что @synchronized делает внутренне. –

+0

Ну, не совсем. Для одного это значительно быстрее. Взгляните на http://perpendiculo.us/?p=133 – Stavash

ответ

5

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

Я буду использовать этот код для демонстрации.

- (int)getNumberEight { 
    @synchronized(_lockObject) { 
     // Point A 
     return 8; 
    } 
} 

- (void)printEight { 
    @synchronized(_lockObject) { 
     // Point B 
     NSLog(@"%d", [self getNumberEight]); 
    } 
} 

- (void)printSomethingElse { 
    @synchronized(_lockObject) { 
     // Point C 
     NSLog(@"Something Else."); 
    } 
} 

Как правило, @synchronized является рекурсивно безопасный замок. Таким образом, вызов [self printEight] в порядке и не вызывает взаимоблокировок. То, что я нашел, является исключением из этого правила. Следующая серия событий вызовет взаимоблокировку и будет чрезвычайно трудно отследить.

  1. Резьба 1 вводит -printEight и приобретает замок.
  2. Резьба 2 вводит -printSomethingElse и пытается приобрести замок. Блокировка удерживается нитью 1, поэтому она находится в очереди, чтобы ждать, пока блокировка не будет доступна и блокируется.
  3. Резьба 1 введите -getNumberEight и попытается приобрести замок. Замок уже удерживается, а кто-то еще находится в очереди, чтобы получить его дальше, так что блоки потока 1. Тупик.

Похоже, что эта функциональность является непреднамеренным следствием желания связать голодание при использовании @synchronized. Блокировка только рекурсивно безопасна, когда ни одна другая нить не ждет ее.

В следующий раз, когда вы попадаете в тупик в своем коде, изучите стеки вызовов в каждом потоке, чтобы узнать, содержит ли какой-либо из заблокированных потоков уже блокировку. В приведенном выше примере кода, добавляя длинные спящие точки в точках A, B и C, тупик можно воссоздать с почти 100% консистенцией.

EDIT:

Я больше не в состоянии продемонстрировать предыдущий вопрос, но есть родственная ситуация, которая до сих пор вызывает вопросы. Это связано с динамическим поведением dispatch_sync.

В этом коде есть две попытки рекурсивно получить блокировку. Первые вызовы из основной очереди в фоновую очередь. Второй вызов из фоновой очереди в основную очередь.

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

Чтобы подчеркнуть, эта функциональность по дизайну, но это может быть неожиданно для некоторых, которые не понимают GCD, а также могли.

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
NSObject *lock = [[NSObject alloc] init]; 
NSTimeInterval delay = 5; 

NSLog(@"Example 1:"); 
dispatch_async(queue, ^{ 
    NSLog(@"Starting %d seconds of runloop for example 1.", (int)delay); 
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:delay]]; 
    NSLog(@"Finished executing runloop for example 1."); 
}); 
NSLog(@"Acquiring initial Lock."); 
@synchronized(lock) { 
    NSLog(@"Acquiring recursive Lock."); 
    dispatch_sync(queue, ^{ 
     NSLog(@"Deadlock?"); 
     @synchronized(lock) { 
      NSLog(@"No Deadlock!"); 
     } 
    }); 
} 

NSLog(@"\n\nSleeping to clean up.\n\n"); 
sleep(delay); 

NSLog(@"Example 2:"); 
dispatch_async(queue, ^{ 
    NSLog(@"Acquiring initial Lock."); 
    @synchronized(lock) { 
     NSLog(@"Acquiring recursive Lock."); 
     dispatch_sync(dispatch_get_main_queue(), ^{ 
      NSLog(@"Deadlock?"); 
      @synchronized(lock) { 
       NSLog(@"Deadlock!"); 
      } 
     }); 
    } 
}); 

NSLog(@"Starting %d seconds of runloop for example 2.", (int)delay); 
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:delay]]; 
NSLog(@"Finished executing runloop for example 2."); 
+0

Использование кода вашего примера (и + [NSThread sleepForTimeInterval:] в диапазоне от 0,1 до 20,0) в точках A, B и C. Я вообще не могу воспроизвести проблему (используя iOS SDK7.1, а также OS X SDK 10.9). Можете ли вы уточнить, как воссоздать проблему? –

+0

Я не могу воспроизвести это (используя iOS 9 с Xcode 7). Звучит не так, во всяком случае - '@ synchronized' не был разработан, чтобы быть« вообще »безопасным с несколькими замками на одном и том же объекте; он был разработан, чтобы быть полностью * безопасным с несколькими замками (в одной и той же теме). Если у вас действительно есть случай, когда это демонстративная проблема, было бы неплохо представить ее Apple (полная рабочая программа для демонстрации ошибки) в качестве отчета об ошибке. –

+0

Я действительно удивлен, что это все еще привлекает внимание. Я изначально заметил эту проблему на iOS 4.x и 5.x много лет назад. Это было написано еще в 2013 году. С тех пор он может быть устранен с улучшениями в компиляторе и в режиме Objective C. – Holly

0

Я наткнулся на это в последнее время, при условии, что @synchronized(_dataLock) делает то, что он должен делать, так как это такая фундаментальная вещь, в конце концов.

Я продолжал исследования _dataLock объекта, в моей конструкции у меня есть несколько Database объектов, которые будут делать их блокировки независимо друг от друга, так что я просто создание _dataLock = [[NSNumber numberWithInt:1] retain] для каждого экземпляра Database.
Однако [NSNumber numberWithInt:1] возвращает тот же объект, что и в том же указателе !!!

Это означает, что я думал, что локализованная блокировка только для одного экземпляра Database не является глобальной блокировкой для всех экземпляров Database.
Конечно, это никогда не было целью дизайна, и я уверен, что это было причиной проблем.

Я изменит

_dataLock = [[NSNumber numberWithInt:1] retain] 

с

_dataLock = [[NSUUID UUID] UUIDString] retain] 
+0

«Я изменюсь» заставляет меня думать, что вы не уверены, что это решит проблему. В любом случае, похоже, вы на самом деле не пробовали. В любом случае, я думаю, что предложение противоречиво: «то, что я считал локализованным блокированием, только для одного экземпляра базы данных не является глобальной блокировкой для всех экземпляров базы данных».Вы уверены, что хотите сказать, что «то, что я считал местным, не глобально»? Это не имеет большого значения для меня. Может быть, вы имели в виду: «То, что я считал местным, глобально»? Если это так, отредактируйте свой ответ и исправьте его. Спасибо! –

Смежные вопросы