Ответ
Блокировка (deadlock) в Go — это ситуация, когда одна или несколько горутин бесконечно ожидают события, которое никогда не произойдет. Чаще всего это связано с неправильной работой с каналами.
Основные причины блокировок с каналами:
-
Отправка в небуферизованный канал без получателя. Небуферизованный канал (
make(chan T)) требует, чтобы отправитель и получатель встретились одновременно. Если горутина пытается отправить данные, а получателя нет, она блокируется навсегда.func main() { ch := make(chan int) ch <- 1 // Deadlock! Нет другой горутины, готовой принять значение. } -
Отправка в заполненный буферизованный канал. Буферизованный канал (
make(chan T, N)) блокирует отправку только тогда, когда его буфер полон, и нет готового получателя.func main() { ch := make(chan int, 1) // Буфер размером 1 ch <- 1 // OK, значение помещено в буфер ch <- 2 // Deadlock! Буфер полон, и никто не читает из канала. } -
Чтение из пустого канала, который никто не закроет. Горутина, читающая из канала, будет заблокирована, пока в него не придут данные или пока канал не будет закрыт.
Способы избежать блокировки:
- Запускать отправителей и получателей в разных горутинах. Это основной паттерн конкурентной работы в Go.
- Использовать
selectсdefaultдля неблокирующей отправки/чтения:select { case ch <- value: // Успешно отправлено default: // Канал заблокирован, выполняем альтернативное действие } - Использовать
selectс таймаутом, чтобы избежать вечного ожидания:select { case val := <-ch: // Получили значение case <-time.After(1 * time.Second): // Не дождались за 1 секунду } - Правильно закрывать каналы. Канал закрывается отправителем, чтобы сообщить получателям, что данных больше не будет.
Ответ 18+ 🔞
А, слушай, смотри, вот эта штука — deadlock в Go — это когда твои горутины тупо встали как вкопанные и ждут друг от друга какого-то пиздеца, который никогда не случится. В основном, конечно, из-за каналов косячат, потому что с ними как с огнём — чуть не туда, и всё, приехали.
Основные причины, почему всё накрывается медным тазом:
-
Пытаешься запихнуть в небуферизованный канал, а читать некому.
Небуферизованный канал (make(chan int)) — это как встреча в подворотне: чтобы передать пачку, нужно, чтобы оба были на месте. Если ты суёшь значение, а получателя нет — всё, ты стоишь там до скончания века, как лох.func main() { ch := make(chan int) ch <- 1 // Ёбта, deadlock! Кто это принимать-то будет? Никого же! } -
Забил буферизованный канал под завязку, а выгребать не начали.
Буферизованный (make(chan int, 1)) — тут уже есть маленькая коробочка на одно значение. Засунул одно — ок. Пытаешься второе запихнуть, а коробка-то полная, и никто не освобождает — опять стоишь, как мудак.func main() { ch := make(chan int, 1) ch <- 1 // Ну вроде норм, лёг в буфер ch <- 2 // А вот тут пиздец! Буфер забит, а читателей нет. Завис. } -
Ты пялишься в пустой канал, который никто и никогда не закроет.
Читаешь из канала, а там пустота, и никто не собирается туда ничего слать. И закрывать не собирается. Ну и стоишь, блядь, до пенсии.
Как не облажаться:
- Запускай отправителей и получателей в разных горутинах. Это же основа основ, ёпта! Один пишет, другой читает — красота.
- Юзай
selectсdefault— это как попытка сунуть, а если не лезет — похуй, идём дальше:select { case ch <- value: // Ура, протолкнул! default: // Канал не готов, ну и хуй с ним, делаем что-то ещё } selectс таймаутом — чтобы не ждать вечно, как последний лох:select { case val := <-ch: // О, пришло! case <-time.After(1 * time.Second): // Ждал-ждал, нихуя. Пиздуй дальше. }- Закрывай каналы правильно. Закрывает всегда отправитель, типа "всё, братан, больше ничего не будет". Получатель это видит и не тупит вечно.