Ответ
Deadlock (взаимная блокировка) — это ситуация в многопоточной системе, когда две или более горутины находятся в состоянии бесконечного ожидания, поскольку каждая из них ожидает ресурс, занятый другой горутиной из этой же группы.
Для возникновения deadlock необходимо одновременное соблюдение четырёх условий (условия Коффмана):
- Взаимное исключение (Mutual Exclusion): Ресурс в каждый момент времени может быть занят только одной горутиной.
- Удержание и ожидание (Hold and Wait): Горутина удерживает как минимум один ресурс и запрашивает другие ресурсы, которые в данный момент заняты другими горутинами.
- Отсутствие вытеснения (No Preemption): Ресурс не может быть принудительно отобран у горутины; он может быть освобожден только той горутиной, которая его захватила.
- Кольцевое ожидание (Circular Wait): Существует цепочка из двух или более горутин, где каждая ожидает ресурс, удерживаемый следующей горутиной в цепочке.
Классический пример на Go:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var mu1, mu2 sync.Mutex
var wg sync.WaitGroup
wg.Add(2)
// Горутина 1: захватывает mu1, затем пытается захватить mu2
go func() {
defer wg.Done()
mu1.Lock()
fmt.Println("Горутина 1: захватила mu1")
time.Sleep(100 * time.Millisecond) // Даем время второй горутине захватить mu2
mu2.Lock()
fmt.Println("Горутина 1: захватила mu2")
mu2.Unlock()
mu1.Unlock()
}()
// Горутина 2: захватывает mu2, затем пытается захватить mu1
go func() {
defer wg.Done()
mu2.Lock()
fmt.Println("Горутина 2: захватила mu2")
time.Sleep(100 * time.Millisecond)
mu1.Lock() // <- Здесь произойдет deadlock
fmt.Println("Горутина 2: захватила mu1")
mu1.Unlock()
mu2.Unlock()
}()
wg.Wait()
}
Способы предотвращения deadlock:
- Соблюдение порядка блокировок: Всегда захватывать мьютексы в одном и том же строгом порядке (например, сначала
mu1, потомmu2во всех горутинах). - Использование таймаутов: Пытаться захватить блокировку с таймаутом (например, с помощью
selectиtime.After), чтобы избежать вечного ожидания. - Использование
sync.RWMutex: Позволяет множественные блокировки на чтение, снижая вероятность конфликтов.
Ответ 18+ 🔞
А, слушай, смотри, вот эта хуйня — deadlock, или по-нашему, взаимная блокировка. Это когда твои горутины, такие умные, встали как идиоты в очередь друг за другом и ждут, пока кто-то освободит ресурс. А ресурс-то занят другим таким же ждуном! И все, пиздец, система встала колом, тишина, только процессор тихо посвистывает.
Чтобы этот цирк случился, нужно аж четыре условия, как в дурдоме:
- Взаимное исключение. Один ресурс — один хозяин. Как последний пирожок в столовой — либо ты его сожрал, либо я, а вместе херачить не получится.
- Удержание и ожидание. Это классика! Горутина, сука, уже вцепилась в один ресурс мёртвой хваткой, а теперь ещё и второй требует, который у другой. Жадная пизда.
- Отсутствие вытеснения. Ресурс нельзя просто так отобрать, как игрушку у ребёнка в песочнице. Только тот, кто захватил, тот и должен отпустить. А он не отпускает, блядь!
- Кольцевое ожидание. Вот тут начинается настоящий театр абсурда. Первая горутина ждёт ресурс от второй, вторая — от третьей, а третья, гадёныш, ждёт как раз от первой! Замкнутый круг, ёпта! Все сидят и смотрят друг на друга, как дурачки.
Вот тебе наглядный пиздец на Go:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var mu1, mu2 sync.Mutex
var wg sync.WaitGroup
wg.Add(2)
// Первая горутина: хватает mu1, потом лезет за mu2
go func() {
defer wg.Done()
mu1.Lock()
fmt.Println("Горутина 1: захватила mu1")
time.Sleep(100 * time.Millisecond) // Подождать, чтоб вторая успела нагадить
mu2.Lock() // А тут она обосрётся и зависнет навечно!
fmt.Println("Горутина 1: захватила mu2")
mu2.Unlock()
mu1.Unlock()
}()
// Вторая горутина: хватает mu2, потом лезет за mu1
go func() {
defer wg.Done()
mu2.Lock()
fmt.Println("Горутина 2: захватила mu2")
time.Sleep(100 * time.Millisecond)
mu1.Lock() // <- Вот здесь, блядь, и будет наш deadlock! Встретились два упыря.
fmt.Println("Горутина 2: захватила mu1")
mu1.Unlock()
mu2.Unlock()
}()
wg.Wait() // И мы тут будем ждать их до второго пришествия, ебать.
}
Как не попасть в эту ловушку для дегенератов?
- Порядок, сука, порядок! Всегда захватывай мьютексы в одной и той же последовательности. Сначала
mu1, потомmu2, и никуда не выёбывайся. Тогда кольца не получится. - Таймауты, ёбта! Не жди вечно, поставь deadline. Попробовал захватить мьютекс — не получилось за N миллисекунд — отъебнись и иди другим путём, не упрямься.
sync.RWMutexв помощь. Если много читателей, а пишет один — используй эту штуку. Она позволяет многим одновременно читать, что снижает вероятность, что все перегрызутся из-за одной переменной.
Короче, думай головой, когда пишешь многопоточность, а то получишь deadlock и будешь сидеть, как Герасим, который Муму утопил, а потом охуевать от содеянного.