1. Достаточно ли lock у self, если меняется ещё и other?
2. Будет ли дедлок, если в каждом swap сначала брать lock у self, потом у other?
Темы
Компании
Ответ
Если a.swap(other: b) будет вызван из одного потока, а b.swap(other: a) из другого, то может случиться следующее:
Поток 1 внутри swap дойдет до строчки isFeatureEnabled = temp (запись в a)
Поток 2 внутри swap дойдет до строчки other.isFeatureEnabled = isFeatureEnabled (тоже запись в a)
Таким образом у нас возникает data race. Хуже того, при записи в один сегмент памяти программа может непредвиденно завершиться.
Data race
Захват обоих lock
Решением может быть захват локов у обоих объектов, чтобы другой поток подождал полного завершения блока кода swap. Однако, если мы просто вызовем:
lock.lock()other.lock.lock()
Поток 1 может успеть заблокировать себя, а поток 2 - себя. Тогда, будет взаимная блокировка потоков (deadlock). Вторая строчка не сможет быть вызвана на обоих потоках.
Deadlock
Порядок локов
Поэтому локи нужно упорядочить. И поскольку мы имеем дело с классами, мы можем воспользоваться их свойством ObjectIdentifier.
ObjectIdentifier это уникальный идентификатор объекта, который еще и реализует протокол Comparable. Что нам это дает:
funcswap(other:UserSettings){let first:UserSettingslet second:UserSettingsifObjectIdentifier(self)<ObjectIdentifier(other){ first =self second = other
}else{ first = other
second =self} first.lock.lock() second.lock.lock()let temp = other.isFeatureEnabled
other.isFeatureEnabled = isFeatureEnabled
isFeatureEnabled = temp
second.lock.unlock() first.lock.unlock()}
Предположим, что ObjectIdentifier(a) меньше, чем ObjectIdentifier(b). Тогда поток 1, зайдя в функцию, сначала заблокирует lock a, а затем lock b. Для потока 2 картина будет иная. Он тоже сначала пойдёт к lock a, который уже заблокирован. Тогда он будет ждать завершения работы потока 1, не создавая deadlock.
Дополнительные вопросы
1. Нет. Lock у self не защищает поле у other. Пока один поток держит только свой lock, второй может писать в тот же isFeatureEnabled через чужой swap. Это и есть data race.
2. Если в каждом swap сначала брать lock у self, потом у other, дедлок как раз появится: a.swap(b) возьмёт a, b.swap(a) возьмёт b, дальше каждый ждёт чужой lock. Поэтому локи упорядочивают через ObjectIdentifier, а не по self / other.