#objective-c #thread-safety #mutex #synchronized
#objective-c #потокобезопасность #мьютекс #синхронизировано
Вопрос:
из «Руководства по программированию потоков» от Apple я прочитал, что «Лучший способ избежать ситуаций взаимоблокировки и блокировки в режиме реального времени — использовать только одну блокировку за раз». Если я предпочитаю использовать директиву @synchronized в своем коде, это означает, что я должен сделать что-то вроде этого:
@synchronized(aObj) {
@synchronized(bObj) { // do sth with the aObj and bObj here }
}
вместо этого:
@synchronized(aObj, bObj) {
// do sth with the aObj and bObj here
}
?? если нет, то что это означает под «Одной блокировкой за раз?». Спасибо…
Ответ №1:
Лучший способ избежать взаимоблокировки — убедиться, что все потоки пытаются блокировать и разблокировать объекты в одном и том же порядке. Вот и все, на самом деле.
Следуя этому простому правилу, вероятность взаимоблокировок равна нулю.
Не имеет значения, пытаетесь ли вы распределить все необходимые блокировки за один заход или за значительный промежуток времени, главное, чтобы порядок был последовательным. Другими словами, никогда не делайте:
Thread A Thread B
======== ========
lock a lock b
lock b lock a
Это может привести к последовательности:
- A блокирует a.
- B блокирует b.
- A пытается заблокировать b, останавливается и ждет.
- B пытается заблокировать a, останавливается и ждет.
Теперь оба потока ожидают от другого освобождения необходимого им ресурса, отсюда взаимоблокировка.
Если вы измените поток B
так, чтобы он блокировался a
и b
в таком порядке, взаимоблокировка станет невозможной.
Комментарии:
1. Полностью ясно! Спасибо. Но правильно ли использовать приведенный выше код (вложенное использование @synchronized)? Никогда не показывайте вложенное использование @synchronized, и я не уверен, что это правильный способ его использования, чтобы избежать взаимоблокировок. Или вы имеете в виду: это правильно:
@synchronized(aObj, bObj)
во всех потоках, а это неправильно:@synchronized(aObj, bObj)
в одном потоке и@synchronized(bObj, aObj)
в другом?2. @Vassilis, я даже не знал, что в ObjC возможна синхронизация нескольких объектов (я использую язык умеренно, но не настолько, чтобы использовать потоки на этом языке). Но мой совет остается в силе, и это подтверждается первым абзацем раздела «Остерегайтесь взаимоблокировок и блокировок Livelocks» в той статье, на которую вы ссылались. При условии, что вы получаете блокировки в одинаковом порядке во всех потоках, взаимоблокировка невозможна. То же самое касается livelock, поскольку ситуация не может возникнуть, если вы выполняете сбор в том же порядке.
3. Еще раз спасибо. Поскольку @synchronized — это просто удобный способ создать блокировку мьютекса POSIX, я предполагаю, что обе части кода в моем вопросе можно использовать, поскольку порядок важен в них обоих. Я сомневался в использовании
@synchronized
, но, похоже, все в порядке.4. Приведенный вами пример не приведет к взаимоблокировке. итак, b заблокирован. Итак, поток a выполняется в b. Затем поток b завершает b и A разблокируется. Tada.
5. @Джим, это приведет тебя в тупик. Рассмотрим следующее. (1) A блокирует a. (2) B блокирует b. (3) A пытается заблокировать b, останавливается и ждет. (4) B пытается заблокировать a, останавливается и ожидает. Теперь оба потока ожидают от другого освобождения необходимого им ресурса, отсюда взаимоблокировка. Я добавлю это к ответу для ясности.
Ответ №2:
Одновременное удержание нескольких блокировок немедленно создает угрозу взаимоблокировки. если два потока получают одинаковые блокировки в разном порядке, они могут в конечном итоге ожидать друг друга вечно. Эта проблема может быть использована для решения принудительного выполнения порядка блокировки.
@synchronized(MIN(a, b))
{
@synchronized(MAX(a, b))
{
// do stuff, safely
}
}
Это работает и с другими конструкциями блокировки. Нравится:
NSLock *a = [[NSLock alloc] init];
NSLock *b = [[NSLock alloc] init];
[MIN(a, b) lock];
[MAX(a, b) lock];
[MAX(a, b) unlock];
[MIN(a, b) unlock];
Ответ №3:
Обе версии вашего кода одинаково злы и ведут в тупик. Этот совет означал, что вы должны написать
@synchronized (obj1) { ... }
@synchronized (obj2) { ... }
У вас есть потенциальная взаимоблокировка, если один поток выполняет вложенные блокировки (используя одну блокировку, затем другую) в порядке, отличном от другого потока. Одно простое правило, позволяющее избежать этого, — никогда не иметь двух вложенных блокировок.