Ответ
Livelock (живая блокировка) — это ситуация в многопоточной системе, когда несколько горутин (или потоков) постоянно выполняют работу, но не могут достичь прогресса в выполнении своих задач. В отличие от Deadlock, где потоки полностью заблокированы и ничего не делают, при Livelock они активны, но их действия взаимно мешают друг другу.
Классическая аналогия: два человека встречаются в узком коридоре. Оба пытаются уступить дорогу, шагая в одну и ту же сторону, затем в другую, и так до бесконечности. Они оба активны, но с места не сдвигаются.
Сравнение Deadlock и Livelock
| Характеристика | Deadlock (Мертвая блокировка) | Livelock (Живая блокировка) |
|---|---|---|
| Состояние потоков | Заблокированы (ожидают ресурс) | Активны (выполняют бесполезную работу) |
| Потребление CPU | Практически нулевое | Высокое, так как потоки постоянно работают |
| Пример | Две горутины захватили по одному мьютексу и ждут мьютекс, захваченный другой. | Две горутины пытаются захватить два мьютекса, но при неудаче освобождают свой и пробуют снова, постоянно мешая друг другу. |
Пример Livelock в Go
Представим двух "вежливых" людей, которые пытаются воспользоваться одной ложкой. Каждый раз, когда один видит, что другой тоже хочет ложку, он уступает.
package main
import (
"fmt"
"sync"
"time"
)
// Общий ресурс
type Spoon struct{
mu sync.Mutex
owner string
}
func (s *Spoon) PickUp(name string) {
s.mu.Lock()
s.owner = name
}
func (s *Spoon) PutDown() {
s.owner = ""
s.mu.Unlock()
}
func main() {
spoon := &Spoon{}
var wg sync.WaitGroup
wg.Add(2)
// Два "вежливых" едока
go func() {
defer wg.Done()
name := "Alice"
for i := 0; i < 5; { // Пытаемся поесть 5 раз
spoon.PickUp(name)
// Проверяем, не пытается ли другой взять ложку
if spoon.owner != name { // Другой успел перехватить
fmt.Printf("%s видит, что ложку взял другой, и уступает.n", name)
spoon.PutDown()
continue
}
fmt.Printf("%s ест.n", name)
time.Sleep(100 * time.Millisecond)
spoon.PutDown()
i++
}
}()
go func() {
defer wg.Done()
name := "Bob"
for i := 0; i < 5; {
spoon.PickUp(name)
if spoon.owner != name {
fmt.Printf("%s видит, что ложку взял другой, и уступает.n", name)
spoon.PutDown()
continue
}
fmt.Printf("%s ест.n", name)
time.Sleep(100 * time.Millisecond)
spoon.PutDown()
i++
}
}()
wg.Wait()
fmt.Println("Обед закончен.")
}
В этом примере, если обе горутины будут работать синхронно, они могут постоянно захватывать и тут же освобождать мьютекс, не давая друг другу поесть. Они активны, но прогресса нет.
Как избежать Livelock?
- Введение случайности: Добавление случайной задержки перед повторной попыткой (exponential backoff with jitter) может помочь потокам "разойтись" во времени.
- Четкий порядок: Установление строгого порядка захвата ресурсов, как и в случае с Deadlock.
Ответ 18+ 🔞
А, ну это же классика, ёпта! Ситуация, когда все вроде как шевелятся, а толку — нихуя. Живая блокировка, Livelock, называется.
Представь, блядь, двух вежливых мудаков в узком коридоре. Один такой: «О, проходи, проходи!» — и шаг в сторону. А второй, тоже не промах: «Да нет, ты первый, я постою!» — и шаг в ту же самую сторону. И они так, сука, синхронно туда-сюда пляшут, потеют, улыбаются, а продвинуться ни на сантиметр не могут. Вот это и есть livelock, блядь. Все в работе, CPU грузится, а прогресс — ноль, пиздец.
Deadlock vs Livelock: кратко и с матом
| Признак | Deadlock (Мёртвый) | Livelock (Живой, но идиотский) |
|---|---|---|
| Состояние | Все стоят как вкопанные и ждут. | Все бегают, суетятся, но нахуй никуда не прибегают. |
| Загрузка CPU | Ноль, тишина, покой. | Овердохуища, все ядра пыхтят. |
| Пример | Два потока схватили по мьютексу и ждут, пока другой отпустит свой. | Два потока пытаются захватить два мьютекса, но если не получается, отпускают свои и начинают сначала, вечно мешая друг другу. |
Пример на Go: «Вежливые обжоры»
Смотри, вот два чудика, Алиса и Боб, хотят суп одной ложкой есть. Такие воспитанные, блядь.
package main
import (
"fmt"
"sync"
"time"
)
// Общая ложка, главный яблоко раздора
type Spoon struct{
mu sync.Mutex
owner string
}
func (s *Spoon) PickUp(name string) {
s.mu.Lock()
s.owner = name
}
func (s *Spoon) PutDown() {
s.owner = ""
s.mu.Unlock()
}
func main() {
spoon := &Spoon{}
var wg sync.WaitGroup
wg.Add(2)
// Алиса, дура вежливая
go func() {
defer wg.Done()
name := "Alice"
for i := 0; i < 5; { // Пытается 5 раз поесть, хы
spoon.PickUp(name)
// Опа, а вдруг Боб тоже хочет? Надо уступить, хороший тон!
if spoon.owner != name { // Ага, блядь, уже не её!
fmt.Printf("%s видит, что ложку взял другой, и уступает.n", name)
spoon.PutDown()
continue // И по новой, ёбта!
}
fmt.Printf("%s ест.n", name)
time.Sleep(100 * time.Millisecond)
spoon.PutDown()
i++ // Ура, один разок поел!
}
}()
// Боб, второй вежливый мудак
go func() {
defer wg.Done()
name := "Bob"
for i := 0; i < 5; {
spoon.PickUp(name)
if spoon.owner != name {
fmt.Printf("%s видит, что ложку взял другой, и уступает.n", name)
spoon.PutDown()
continue
}
fmt.Printf("%s ест.n", name)
time.Sleep(100 * time.Millisecond)
spoon.PutDown()
i++
}
}()
wg.Wait()
fmt.Println("Обед закончен.")
}
Если они синхронно попадут, то будет пиздец. Схватит Алиса ложку, а Боб тут как тут — она её бросит. Схватит Боб — она уже опять тянется, он тоже бросает. И так до второго пришествия, в рот меня чих-пых! Оба активны, оба «работают», но суп холодный останется.
Как не словить эту хуйню?
- Добавить хаоса, блядь. Сделать случайную паузу перед повторной попыткой. Пусть один подождёт чуть дольше — тогда второй успеет всё сделать. Это как в драке — кто первый выждал, тот и прав.
- Жёсткая иерархия. Чётко договориться, кто и в каком порядке ресурсы хватает. Как в армии: сначала старший по званию, потом младший. Никакой вежливости, только жёсткий порядок, ебать.
Короче, livelock — это когда все слишком стараются быть хорошими и умными, а в итоге получается ебаный цирк с конями. Иногда надо быть проще и наглее, и тогда всё заведётся.