Ответ
Взаимная блокировка (Deadlock) — это ситуация, когда два или более потока находятся в состоянии бесконечного ожидания ресурсов, захваченных друг другом.
Условия возникновения (Условия Коффмана):
- Взаимное исключение: Ресурс не может быть использован несколькими потоками одновременно.
- Удержание и ожидание: Поток удерживает один ресурс и ждет другой.
- Отсутствие вытеснения: Ресурс нельзя отнять у потока.
- Циклическое ожидание: Возникает круговая зависимость потоков по ресурсам.
Классический пример на Swift (с NSLock):
let lockA = NSLock()
let lockB = NSLock()
DispatchQueue.global().async {
lockA.lock() // Поток 1 захватывает lockA
sleep(1) // Имитация работы
lockB.lock() // Поток 1 ЖДЁТ lockB (но его держит Поток 2)
// Критическая секция...
lockB.unlock()
lockA.unlock()
}
DispatchQueue.global().async {
lockB.lock() // Поток 2 захватывает lockB
sleep(1) // Имитация работы
lockA.lock() // Поток 2 ЖДЁТ lockA (но его держит Поток 1)
// Критическая секция...
lockA.unlock()
lockB.unlock()
}
// Оба потока заблокированы навсегда.
Способы предотвращения:
- Упорядочивание блокировок: Всегда захватывать ресурсы в одинаковом глобальном порядке (сначала A, потом B).
- Использование
NSLock.try()или блокировок с таймаутом. - Отказ от явных блокировок в пользу высокоуровневых механизмов GCD (очереди,
DispatchSemaphore,DispatchGroup). - Проектирование без состояний или с использованием потокобезопасных структур данных.
Ответ 18+ 🔞
Да ты посмотри, какая дичь может случиться в коде! Взаимная блокировка, или deadlock, если по-ихнему. Это когда твои потоки, эти ебушки-воробушки, встают в позу и намертво друг друга ждут. Как два упыря в подъезде: один держит дверь, другой — ключ, и оба нихуя не двигаются, потому что каждый ждёт, пока второй отвалит.
Чтобы эта пиздопроебибна случилась, нужно аж четыре условия, как в плохом анекдоте. Условия Коффмана, блядь, звучат.
- Взаимное исключение. Ресурс — штука единоличная, как последняя пельмешка. Одному дали, второй пусть постоит, поголодный.
- Удержание и ожидание. Поток — хитрая жопа. Одной рукой пельмешку зажал, а второй тянется за соусом, который у другого.
- Отсутствие вытеснения. Отобрать пельмешку силой нельзя. Раз взял — жуй до конца, пока сам не отдашь.
- Циклическое ожидание. И вот тут начинается цирк. Первый ждёт соус у второго, а второй ждёт пельмешку у первого. И оба нихуя не жуют, просто смотрят друг на друга. Замкнутый круг, пиздец.
Вот смотри, как это выглядит в коде, прям классика жанра:
let lockA = NSLock()
let lockB = NSLock()
DispatchQueue.global().async {
lockA.lock() // Первый чувак схватил замок А
sleep(1) // Подумал, почесал
lockB.lock() // Пытается взять замок Б, но он уже у второго! Ждёт...
// А сюда он никогда не попадёт, блядь
lockB.unlock()
lockA.unlock()
}
DispatchQueue.global().async {
lockB.lock() // Второй чувак сразу цапнул замок Б
sleep(1) // Тоже подумал
lockA.lock() // Тянется за замком А, а он у первого! Тоже ждёт...
// И здесь — тишина, вечный покой
lockA.unlock()
lockB.unlock()
}
// Итог: два потока, два замка и нихуя не происходит. Накрылись медным тазом.
Ну и как с этой хуйней бороться? А вот как, слушай сюда:
- Порядок, сука, порядок! Самый надёжный способ. Всегда договаривайся брать ресурсы в одной и той же последовательности. Сначала всегда
lockA, потом ужеlockB. Тогда циклического ожидания не будет — второй поток просто подождёт, пока первый всё отпустит, и спокойно возьмёт оба по порядку. - Попробуй, но не настаивай. Используй
try()или блокировки с таймаутом. Не получилось взять второй ресурс — отпусти первый, подожди немного и попробуй снова. Не надо упорствовать, как Герасим с Муму. - Не выёбывайся с низкоуровневыми замками. GCD, блядь, для чего придумали? Используй очереди, семафоры, группы — там много умных штук, которые помогают эту проблему обойти.
- А может, вообще без этого? Самый крутой способ — спроектировать так, чтобы не было общих изменяемых состояний. Идеал, конечно, но стремиться к нему надо.
Вот так вот, блядь. А то напишешь такой deadlock, а потом сиди и думай, почему приложение виснет, а терпения ебать ноль.